One of the big improvements that came in JUnit 5 was support for dependency injection via constructors and methods. Since the release of JUnit 5 in September 2017, third-party libraries, like mockito, have started providing native support for constructor and method injection. In this article we will take a quick look at how to use constructor and method injection with mockito and then look at why you should start injecting mocks as method arguments in your automated tests.
How to Inject a Mock as an Argument
Starting with 2.21.0, current version 2.25.0, mockito has provided support for injecting mocks as both constructor and method arguments. Let’s looks at how you can start using dependency injection with mockito.
In your test class you will need to annotate it with
@ExtendWith(MockitoExtension.class). Then for any arguments you would like mockito to provide a mock for, you simply annotate the argument with
@Mock. Here is an example of using mockito dependency injection in action:
Pretty simple and straight forward. Let’s now look at why you should start using method injection of mocks.
The Case for Injecting Mocks as Method Arguments
There are three major benefits that come from automated testing: speed, repeatability, and auditability. The first two are pretty well understood benefits of automated testing, auditability however is if not less well understood, definitely less often discussed. Auditability, within the context of automated testing, refers to the quality of being able to see what code has been tested and the intent of the test.
Code coverage can be achieved without spending much time thinking about how other people, developers, test engineers, business analyst, etc, might use automated tests to understand (i.e. audit) the system the tests are covering. Tests with names like
testFail2, can be executed just fine, but do little to communicate their intent. For an automated test suite to be properly auditable, tests names need to clearly convey the intent of what behavior is being tested. While a test with a name of
testRollbackAddUserAddressValidationError is a bit of a mouth full, it pretty clearly describes what scenario the test is covering.
testRollbackAddUserAddressValidationError() provides intent, to understand the scope of the test, what dependencies the code under test interacts with, it would require inspecting the code within the test case itself. However we can begin to communicate scope by injecting mocks as method arguments. If we were to do that with the above test we will would have
testRollbackAddUserAddressValidationError(@Mock UserDao userDao). Now just from reading the signature of the test case we can determine that the scope of the test also includes interacting with the
When executing tests as a group, we can better see the benefits of injecting mocks as method arguments. Below is an example of running two test classes performing the same set of tests, but one is using mocks at the class level, while the other is using method injection. From the JUnit report alone, we can understand that
UserService depends upon the
Note: Another new feature in JUnit 5 are nested tests, which is being used here.
Injecting mocks as method arguments isn’t game changing, but it can help make tests easier to read, and thus audit, thru being able to communicate in the signature the scope of the test. While there will be instance where passing a mock in as a method argument isn’t practical, generally that should be rare*, and so hopefully this article encourages you to generally use method injection when you are working with mocks.
* Complex mock setup should be seen as a smell that either (or all) the mock, the test, or the code under test has too many responsibilities