Self-registered backdoor data (stub)
Stubbing and mocking are often used interchangeably, in part due to their subtle differences. Both stubs and mocks can provide the ‘system-under-test’ or SUT with information via indirect inputs that are not directly available in the test environment. However, the mock’s main goal is to verify certain expectations during this indirect interaction, while stubs only provide the information.
Stubbing is wildly used in integration tests. There is probably no integration test suite that couldn’t benefit from them. Discovering that you need a stub or any kind of test fixture is the consequence of pinpointing the actual SUT in your production code. However, the SUT is not always clear – especially in integration tests. Usually, you want to test only a portion of your system, but defining the area and guarding the boards of that area is not always easy. Stubbing is often a part of the answer.
This post will only handle code stubbing, but remember that stubbing isn’t limited by code. Test files, environment variables, and even temporary Azure resources can be considered stubbing, as they provide data that can only be passed to your application indirectly. This is another reason way in which stubbing is different from mocking.
Stubbing in integration test code often includes in-memory implementations. These kinds of implementations should not be used in production but are excellent for stubbing, as they place the tester fully in control of the scenario. As in-memory implementations are very common, we at Arcus have made some of our test fixtures public so they can be used in every integration test suite. This example will provide you with a test that requires the Arcus secret store. This secret store is used in applications to retrieve secrets in a safe, central and durable way, which of course uses real implementations in production.
Application code that uses the secret store will get the implementation of
ISecretProvider injected. However, it will still use the in-memory version behind the scenes. This is a great example of useful, real-life self-registered backdoor data implementations or stubbing.
Self-registered backdoor assertion (mock)
To assert on the ‘backdoor’ of your application, we use ‘mocking’. Different from stubbing is that the focus here lies on the assertion part and not on the backdoor data. Recently, the Arcus team came across a very good example of where custom test fixtures can be used for mocking. It was related to asserting a method of an API controller. The test would send an HTTP request and we would assert the received HTTP request at the controller. The problem that arises here is that you would be tempted to add the assertion directly in the API controller, hiding the assertion from the test. That would be a mistake, as the test reader won’t see what the test is verifying simply by looking at the test code. Here, self-registered backdoor assertions are a great way to solve the problem.
Imagine an API controller, where you need to verify if an HTTP request header is present:
The test would be something like the following. Note that the
WebApplication is a fictive test fixture that sets up a complete API application with services, API controllers, and other startup functionality so we can test our fictive
The problem that self-registered mocks solve is that the test normally doesn’t have access to the place in which you want to verify the situation. In this case, the dependency injection system will create the services outside the test’s control. Adding a dedicated ‘assertion service’ to the dependency injection system solves this problem rather elegantly. Just make sure that you can configure your assertion service from the test so that the test reader can clearly see what is being verified.
This last example moves the assertion to the test code, which is the place where all assertions should be located. Mock assertions are often noticed by the location in the test, as their backdoor behavior means they are registered before the ‘act’ phase of the test.
Self-registered decorator assertion (mock)
A slight variation on the common backdoor mock assertion where the assertion is injected into the system under test is when the assertion is placed as a ‘decorator’ around the to-be-tested system. Different from the previous technique is that we no longer have sole control over the creation of the system, and we also don’t have access to the injected services. In this case, we can’t inject the system with a
HttpAssert. These situations occur when the type you want to test is not under your control, but the configuration is. Usually, the functionality you want to test is also a form of decorator, as it would be useless to test someone else’s code only.
HTTP client message handlers are a good example of this scenario. Imagine a custom HTTP client message handler you want to test, but (1) you don’t control the creation process of the
HttpClient and (2) you can’t inject additional assertion functions for that client. Usually,
HttpClients are created using the
IHttpClientFactory as it provides a central and consistent way of configuring your clients throughout your application.
To test that the custom HTTP client message handler is solely responsible for adding the header, we should assert as close to the message handler as possible. We can also verify this at the receiver’s level, but if we can, we should also assert on this level as we will be more sure that this message handler only is responsible for adding the header. Moreover, we may not control the receiver’s side.
We can borrow some techniques from the previous section to inject our assertion into our decorator, to show the test reader what we are asserting.
This pattern is very useful in these scenarios, as it still provides enough clarity on the assertion without you having to assert in big end-to-end scenarios where the assertions may be more unstable due to environmental circumstances (such as middleware, proxies, gateways).
This post went over some of the test fixtures that are useful in tests where the type is registered in a dependency injection system. This means that the creation of your system-under-test is not always in your control. Injecting data or assertions, or even decorators is a way to solve this problem by working together with the dependency system. It’s also worth noting that functional programming helps with clarity as the assertion function in mocks lets you define your assertion code in the test itself, rather than hidden away at some obscure location.
Test code is sometimes overlooked if it comes to clean code, refactoring or maintainability. It is, however, the first place people look to see how the code works and should therefore be as simple as possible. The arrange/act/assert phases help a great deal — now it is our job to show them in the test.
Thanks for reading!
Subscribe to our RSS feed