Question

I've done a lot of test writing using Mocks, and so I've learned that it makes refactoring difficult due to implementation coupling inherent with Mocks. I've done a lot of reading on the topic tonight, but cannot see a way to unit test DI'd facade-type classes without using Mocks. I would like to know the correct way to test something like a Controller in an MVC application (say, Spring) with the intent on not using mocks.

For example, given a Controller that depends on a Service. How can I test the controller without mocking the service.

public class ThingController {
  ..
  public getAllThings() {
    return thingService.getAllThings();
  }
}

To test getAllThings(), my natural thought is to test this by injecting a mock of ThingService to the controller that returns some things when getAllThings() is invoked.. But I can tell that this immediately makes the test depend on the controller's implementation in calling the service's getAllThings().

What is the preferred non-coupled approach? Is it that in a case like this a "Unit" test is just not worth it? And instead we prefer a "Component" test that sets up an actual (or fake) service with data and then verifies that the controller returns all things populated in said injected service?

Edit: For more disclosure/explanation, my Controller tests (in Spring) are usually not pure unit tests, as they're done using @WebMvcTest, but services are injected as Mocks. And to avoid mocking the implementation, I'm seeking an alternative. I didn't mention specifics, because I wanted to include other scenarios like unit testing Services: in this case, I want to avoid mocking injected repositories.

Was it helpful?

Solution

Unless you want to re-architect your whole solution, then only other option is to use Fakes instead of mocks.

Fake is real implementation of an abstraction, but which is intended for testing and has limited real functionality. Best example are in-memory repository implementation instead of repository using SQL. Or fake service implementation that behaves as same as real service, but has in-memory data storage and lacks all the bell-and-whistles of real service.

This fake implementation can then be re-used across all places where the abstraction can be used. And in some scenarios, it is possible to write tests that verify that fake and real implementations behave in same way.

Fakes do not cause tight coupling between tests and code, like Mocks do. This is because tests only know about test instance being used, not about the interface implemented and consumed. That is hidden detail. And when expected real implementation changes, simply changing the fake in single place should properly propagate across all your tests, so you don't have to double-check all your tests that they have correct expectations in their mocks.

Now some might say "That is not real unit tests." and while I might somewhat agree, I just don't care. Fake implementation results in tests that are fast, isolated, independent and easy to maintain. All what I look for in good test. Unit or not.

OTHER TIPS

You cannot test a controller using a real instance of a service and still call it a unit test. Unit tests are meant to run with in-memory data, and a single test should execute within a few milliseconds (at the most). Unit tests should also not utilize any out-of-process resources, like file systems or databases.

That isn't to say testing a controller without mocks is an invalid test. It would be considered an integration test. The advantage of such a test is it hits more code. The disadvantage is that integration test are more difficult to debug because they hit more code.

Licensed under: CC-BY-SA with attribution
scroll top