Geni Jaho
3 min readCountless are the times I've had to test for validation errors, and countless are the times I've repeated the same code for this purpose. Duplicating testing code is not always a bad thing though, but for validation errors, I don't know, it doesn't feel right. If it is acceptable to test one FormRequest with duplicated code, it's a sin to do that for a whole project. And I've been guilty. 🤣
What I'm talking about looks like this:
Every method has the same setup phase, the same action, and the same assertions. The only difference is the data being provided with the request, and the errors that are expected to be returned. I don't usually like to check for specific error messages, as it makes the tests more fragile. The error key should suffice most of the time.
The obvious solution here is to refactor into a method that encapsulates the common logic. Let's do that and see what we get:
This looks a lot better and cleaner. However, we can go a step further, as PHPUnit offers some helper methods called Data Providers. Essentially, a data provider is a method that returns data that other tests depend on. This allows you to execute a single test multiple times, but with different input, which is perfect for our example. The data provider must be a public
method, and return a list of input values. The test that uses this provider will run for each element in the list.
To utilize data providers, we simply annotate the method on the tests we want it to run with. Our finished example looks like this:
Pretty neat, right? When you run the test it shows that it's run 4 times, each with its own data, like this:
Also, when one of the tests fails, you'll get a message indicating which dataset is making the test fail. So helpful. You can even provide keys, and you should, to the data provider array; those keys will be shown on the test results instead of with data set #0
.
Form validation testing is not the only use-case for data providers though. They're very helpful in any case where you need to test an object's implementation with different input values, or testing switch
statements, etc.
The code used for illustration is taken from the OpenLitterMap project. They're doing a great job creating the world's most advanced open database on litter, brands & plastic pollution. The project is open-sourced and would love your contributions, both as users and developers.