Testing Successful Form Submission and Its Side Effectsfor Drupal 8 , 9 , 10 , and 11

Last updated :  

Validation passing is only half the story — a form that correctly rejects bad data but then does the wrong thing with good data is still broken. This lesson tests the other half: submit genuinely valid input, and prove the form's submitForm() method actually did what it claims to — saved the right entity, wrote the right config, redirected to the right place.

What you'll learn in this lesson

  • How to submit valid data and assert the resulting side effect actually happened
  • Checking database/entity state directly after a functional-test submission
  • Asserting a post-submission redirect landed where it should
  • Why "the success message appeared" isn't proof enough on its own

Submitting valid data and checking the real effect

public function testValidSubmissionSavesEntity() {
  $this->drupalGet('/contact/add');

  $edit = [
    'name' => 'Ada Lovelace',
    'email' => 'ada@example.com',
  ];
  $this->submitForm($edit, 'Save');

  $assert = $this->assertSession();
  $assert->pageTextContains('Created new contact Ada Lovelace.');

  // Don't just trust the message — verify the actual side effect.
  $storage = \Drupal::entityTypeManager()->getStorage('content_entity_example_contact');
  $contacts = $storage->loadByProperties(['name' => 'Ada Lovelace']);
  $this->assertCount(1, $contacts, 'Exactly one matching contact was actually saved.');
}

The confirmation message is the least reliable thing to assert on here — it's just a string, and it's entirely possible for a bug to leave the correct message hardcoded while the actual save logic silently fails. Loading the entity back out of storage and checking it's really there is what actually proves the submit handler did its job.

Asserting a redirect landed correctly

$assert->addressEquals('/contact');

If your submitForm() calls $form_state->setRedirect('some.route'), this is how you prove it actually fired. Combine it with the entity-existence check above and you've verified the full contract: data was saved, and the visitor ended up where they were supposed to.

For config forms specifically, replace the entity-storage check with reading the config object directly: $config = $this->config('my_module.settings'); $this->assertEquals('expected value', $config->get('my_key')); — same principle, different storage layer.

Quick check: your test asserts pageTextContains('Created new contact') and it passes, but a teammate says the feature is actually broken in production — no contacts are being saved. What did the test likely miss? (It probably only checked the rendered message, not the actual entity in storage — exactly the gap this lesson's example closes by loading the entity back out and asserting it's really there.)

Key takeaways

  • Never assert only on the confirmation message — load the actual saved entity, config, or database row and assert on that directly.
  • $storage->loadByProperties([...]) after a submission is the reliable way to prove a save actually happened.
  • Use addressEquals() to verify a post-submission redirect went where setRedirect() intended.
  • Test both the happy-path message AND the underlying state change — they can diverge if there's a bug between the message and the real save logic.

Coming up next

Standard form submission is straightforward to test since the whole page reloads. AJAX-driven forms are trickier — next, we cover how to simulate AJAX callbacks in a functional test and assert on the partial page updates they produce.