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