jbreuer95's avatar

More useful form validation feature tests

I built a big CMS system so it was time I started learning how to test, whoops. I watched a bunch of videos but testing form validation rules seemed like a lot of work that isn't even that useful. You aren't really testing your own code, you are testing the validation rules Laravel provides. Yes, it comes in handy when you remove a validation rule unintended but how often would that happen?

For instance, I have the following form request:

[
  'identification' => 'required|string|max:255',
  'picture' => 'required|string|max:255',
  'first_name' => 'required|string|max:255',
  'last_name' => 'required|string|max:255',
  'email' => 'required|string|email|max:255|unique:users',
  'password' => 'required|string|min:6|confirmed',
  'pin' => 'required|integer|digits:4',
  'street' => 'required|string|max:255',
  'street_number' => 'required|integer',
  'street_number_addition' => 'nullable|string|max:255',
  'postalcode' => 'required|string|max:255',
  'city' => 'required|string|max:255',
  'region' => 'nullable|string|max:255',
  'country' => 'required|string|max:255',
];

The number of tests I would need if only testing the negatives.

  • required: 12
  • string: 12
  • max: 11
  • integer: 2
  • unique: 1
  • nullable: 2
  • digits: 1
  • min: 1
  • email: 1
  • confirmed: 1

That's a total of 44 tests and if doing the positives too that adds at least another 44

But most of these tests (40), except for email, confirmed, min(password) and digits are just to make sure you don't input data into your database that won't fit. So I got the idea to analyse the database for the type, length and nullability. then test if the form I'm testing checks for that gracefully. That way if I change the DB i don't have to change my tests but it will let me know if I forgot to update the form validation, a useful test :).

Okay so this is what I got so far:

/** @test */
public function a_userform_validates_all_required_db_fields()
{

    $this->signInAs('admin');

    $fields = DB::connection('mysql')->select("
        SELECT COLUMN_NAME
        FROM information_schema.COLUMNS
        WHERE TABLE_SCHEMA = ?
        AND TABLE_NAME = ?
        AND IS_NULLABLE = 'NO'
    ", [ config('database.connections.mysql.database'), 'users' ]);

    foreach ($fields as $value) {
        $field = $value->COLUMN_NAME;
        if ($field !== 'id') {
            $request = UserFactory::store($this, [$field => '']);
            $name = str_replace('_', ' ', $field);
            $request->assertSessionHasErrors([$field => "The {$name} field is required."]);
        }
    }
}

PS. I have some helper functions here specifically for my project.

Nothing is perfect, I have to use my real DB connection to get access to the information_schema table because SQLite doesn't have this. I can make this more generic so that I can use it easily with the almost hundred forms I have in this system.

I still need to write tests for length and type. But in the meantime, I would love to know what people think of this method, and if I can do something better or different.

0 likes
4 replies
martinbean's avatar

You aren't really testing your own code, you are testing the validation rules Laravel provides.

Well, no. You’re testing how your application applies the validation rules Laravel provides.

Yes, it comes in handy when you remove a validation rule unintended but how often would that happen?

But it might happen. Just today I’ve literally had a test pick up an instance where I somehow managed to add a typo to a parameter. You don’t get burgled every day but you still buy home insurance, right…?

For the “repetitive” tests, you can leverage PHPUnit data providers. This allows you to re-run the same test case with different parameters each time. I use them in the case you highlighted: lots of fields having a required validation rule:

class CreateArticleTest extends TestCase
{
    /**
     * @dataProvider requiredFieldsDataProvider
     */
    public function testFieldIsRequired($field)
    {
        $this->createArticle([$field => ''])
                 ->assertValidationError($field);
    }

    public function requiredFieldsDataProvider()
    {
        return [
            ['headline'],
            ['summary'],
            ['body'],
        ];
    }

    private function createArticle(array $overrides = [])
    {
        $defaults = [
            'headline' => 'Test Headline',
            'summary' => 'Test summary',
            'body' => 'Test body',
        ];

        return $this->post('/articles', array_merge($defaults, $overrides));
    }
}

This will run the testFieldIsRequired case three times, once with each of the items from the data provider. I simply set the specified key to be an empty string, and assert that I get a validation failure.

jbreuer95's avatar

@MARTINBEAN

Well, no. You’re testing how your application applies the validation rules Laravel provides.

Fair enough

But it might happen. Just today I’ve literally had a test pick up an instance where I somehow managed to add a > typo to a parameter. You don’t get burgled every day but you still buy home insurance, right…?

Absolutely that's why i created this type of test

For the “repetitive” tests, you can leverage PHPUnit data providers.

This is what i had before, i like data providers a lot too. I might even go that route, I just thought it would be cool to make a test based on the database structure.

I think the data providers are the way to go if you do TDD. I unfortunately have a huge test less system already and this comes in handy checking if i made the right validations afterwards. Hell i might even do both

martinbean's avatar

I just thought it would be cool to make a test based on the database structure.

Not really, because you’re then not testing how your application actually behaves.

If you update your database schema but don’t update validation in a controller that writes rows to that table, then you’re going to end up with bugs due to SQL errors, because your validation is no longer in line with the database schema and might start letting in values that your database schema no longer considers valid.

jbreuer95's avatar

@MARTINBEAN - That is exactly what this test is for?

For instance, if i edit the schema so a field is required that wasn't before and i don't update the form validation this test will start to fail letting me know i have to update the validation rules

Please or to participate in this conversation.