Factory fixtures, what in the whole earth would that be? When you want to test your code thoroughly, you have to write quite a few tests. This way, you ensure that your code is working as expected. Nevertheless, you might face the following problems:

  • A simple change that is not related to existing tests might break a lot of tests (e.g., a new required attribute is added to an object).
  • Tests are unreadable due to a lot of setup code (e.g., only one attribute is relevant for the test, but you have to create a complete object with 5 attributes).

We can solve these problems by using factory fixtures. Factory fixtures are fixtures that return functions to create objects – as opposed to regular fixtures that return already created objects. Inside them, we can apply sane defaults and override only the relevant attributes for the test. Enough talking; let’s take a look at the examples.

Password validation

Let’s say we have a user model with the following password requirements:

  • At least 8 characters
  • At least one uppercase letter
  • At least one lowercase letter
  • At least one special character
  • At least one digit

The user model might look like this:

Copy

Tests without factory fixtures might look like this:

Copy

Tests with factory fixtures might look like this:

Copy

Before we compare the two examples, let’s refresh our memory of what pytest fixtures are. pytest fixtures are decorated functions that provide data/objects/… to our tests. They are executed before the test function by pytest. Their result is passed to the test function as an argument. We can reference fixtures in our tests by adding arguments with matching names to the test functions.

Readability Is Improved With Factory Fixtures

Let’s now compare the readability of the two examples above. In both cases, we’re testing only password validation. To cover all of our requirements, we have to write 6 tests. That’s the same for both cases. Anyhow, there are some differences to mention.

As you can see, the first example requires us to create a user object with all the required data inside our test cases. That’s required even though password is the only relevant value for the tests. This hurts the readability of our tests.

On the other hand, the second example uses a factory fixture to create user objects. Using factory fixtures, we clearly communicate that the only relevant thing for our tests is the password. This makes our tests much more readable. Especially, when the model has more attributes. This means that we need to write less code while improving the readability of our tests. That’s a perfect win-win situation.

Factory fixtures improve resistance to unrelated changes

As we’ve seen, readability is improved by using factory fixtures. Not only that, but our tests are also more resistant to unrelated changes. Let’s say we add a new required attribute to the user model – date_of_birth. What will happen with our tests?

In the first example, we need to go and update all of our tests to include the new attribute. This is not a big deal for 6 tests, but what if we have 1000 tests that depend on the user model? This becomes a nightmare. A simple change in implementation, a significant change in tests. That’s not what we want.

Looking at the example using factory fixtures, we can see that only one change is required. We need to set additional attributes inside the factory fixture. That’s it. We don’t need to touch any of our tests. A simple change in implementation, a simple change in tests. That’s what we want.

See example:

Copy

Change in tests with factory fixtures:

Copy

This way, we don’t need to touch our password tests when a new (non-password-related) attribute is added to the user model. We would still need to touch them if password validation logic changes, but that’s expected.

Conclusion

As you can see, factory fixtures are a great way to improve our tests’ readability and resistance to unrelated changes. They might require a bit more setup, but they pay off in the long run.

You can find code examples in this Github repository.

Subscribe To Python Testing Tips

Get Python Testing Tips to Your Inbox

Subscribe To Python Testing Tips

Get Python Testing Tips to Your Inbox

Share This Story, Choose Your Platform!