2

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.

1
  • 2
    Don't unit test the controller; integration tests can check that the right routes do the right things.
    – jonrsharpe
    Aug 29 '20 at 18:33
7

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.

1
  • Thank you. As I also mentioned in my question, it seems like "Fakes" are the way to go. I definitely think a test using fakes is still a Unit Test as a Fake is just a certain type of Test Double, as is Mock. You've helped me feel more confident that my thought evolution is going in the write direction for decoupling tests from implementation. It's been getting more and more smelly recently :P
    – rewolf
    Aug 30 '20 at 0:38
5

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.

8
  • I don't think Unit test is that clearly defined. At our company we have tests that read and write to a database and still call them Unit tests. Though I do agree that there is a benefit in having tests that finish quicker than that.
    – Helena
    Aug 29 '20 at 19:49
  • The distinction between unit, integration, and functional tests does not have a unified definition. I had a coworker tell me once that manual end-to-end tests were "unit tests." Not times than not I've heard into tests described as fast and not using anything today resides out-of-process. Aug 29 '20 at 20:17
  • 1
    @Helena best unit test definition comes from Michael Feathers. Even if you disagree on what to call them, this is how tests should be separated. I need a group of tests so fast I can run all of them with every compile. Can do that if you mix in the slow tests. Aug 29 '20 at 21:28
  • @GregBurghardt Thanks for you response. My first thought as an alternative is not a real service instance, but rather a Fake. I believe Fake dependencies would still let it be called Unit test. If providing a real instance I agree it would need to be have a configuration without side effects/external deps. Models instances, rather than mocks, can be used in Unit Tests for example.
    – rewolf
    Aug 30 '20 at 0:36
  • 1
    @rewolf, Okay, that is not the definition I usually work with. I work under the definition given by "The art of unittesting". Which is; A Stub is a fake that you don't assert on, and a Mock, is a fake that you do assert on. I always talk about Fakes, as the category of different substitutions. Aug 31 '20 at 12:12

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.