all Technical posts

Maintainable Inputs in Tests Contexts with Argument Pattern

Test contexts are critical for readable and maintainable integration tests. When problems arise, it is usually with the provided inputs.

When you just want to change things

Test contexts centralize certain common functionality for maximum reusability. Because of this, the provided functionality should often be written in such a way that it works both with and without arguments. Optional parameters are a way to do this, but the downside is this only works for constant values. Once you want it to default to an ‘at runtime known value’, you have to come up with another solution.

Here’s an example of a request that you could want to default to some value:

Now imagine that you want to generate this value so the test result is more stable. If the inputs are simple enough and you do not have as many When... functionality, you could get away by placing the generative code right in the method and mark this with a string tag = "auto-generate" optional parameter of sorts. But the moment you have several or you have to call several When... methods for your scenario, it becomes too complex. It’s too implicit.

Make implicit requirements explicit

We normally generate a value for two reasons: we want to have a stable test result for many different kinds of the same sort of inputs; or we actually do not care about the concrete value, just that there is one. Because of this, we should not burden the test body with the generative code (as long as the test scenario does not mention the input). Placing all the generative code in the test context is an option, but we should have a maintainable approach to overriding this ‘ignored’ or ‘generated’ value. Also, it could be that you like some inputs to be ignored or overwritten, but for others, you would like to preserve the value. This functionality is too complex to write in pure conditional statements in your test context.

The argument pattern helps in this case, as it makes this implicit functionality explicit:

With some additional concrete type examples, we can make the generative and ignored code more explicit:

Simplify test context

If we include this pattern in our test context, you will see that the implicit ‘ignored’ and ‘overwritten’ functionality is now clear for the test reader.

👀 Notice that we can add additional implicit operators on the Arg type to make sure that it translates automatically to the type of input (in this case string and DateTimeOffset). This way, you use the special Arg type as any regular string value.

Conclusion

This pattern will normally grow naturally from refactoring cycles. You will see similar functionality pop up across your test contexts and want to centralize it. The argument pattern is a great way to make this explicit and unburdens the test context of its complexity.

Test contexts are susceptible to becoming God-like classes and approaches like this one makes it more easily to maintain your inputs for your integration test scenarios.

Thanks for reading!
Stijn

Subscribe to our RSS feed

Hi there,
how can we help?

Got a project in mind?

Connect with us

Let's talk

Let's talk

Thanks, we'll be in touch soon!

Call us

Thanks, we've sent the link to your inbox

Invalid email address

Submit

Your download should start shortly!

Stay in Touch - Subscribe to Our Newsletter

Keep up to date with industry trends, events and the latest customer stories

Invalid email address

Submit

Great you’re on the list!