1

Assuming I’ve a class with three methods, startCollection, add, endCollection.

One test case is: if start was not called, add should return an error.

I’ve mocked the persistency storing the state with an memory variant, and test it. All fine.

Now I’ve yet another class: a RestController. It also has 3 methods that call the relevant service methods and then maps the error to a status code + JSON error.

Here the very same is true. If a POST to add is performed, it should return an error if start wasn’t called. Here i can literally copy paste the mock object from above, inject it into the service, and inject the service in the controller.

But i feel weird having the same test twice. Only that the return is different.

Should the controller unit test simply not check that add returns an error when start isn’t called, but just that it maps the error and success successfully?

3

There are different ways for looking at this.

Viewpoint: only write value-add tests. Test the behaviour that “adds value” to the overall software. Don't bother mocking and isolating everything if that's too much effort. If your REST controller has logic for mapping exceptions to return codes, then do write a test for that. But if that behaviour is part of your web framework, you likely shouldn't bother. You can trust that other components are doing their job correctly.

Viewpoint: only test externally visible behaviour. A REST client cannot assume how the server is architected internally, and doesn't know (or care) about whether the startCollection() method was called. If it is important that the POST /add endpoint return an error if POST /start was not invoked previously, the REST controller needs a test for that.

Personally, I think that overly detailed unit tests with lots of mocking can often be a waste of time. Yes, TDD is very nice. Yes, unit testing complicated code is awesome. But a lot of code is not complicated, it's just glueing different parts together. Testing such glue code in isolation would require an insane amount of mocking. That effort often stands in no relation to the value of these tests. Such isolated test also fail to exercise the part of the code where, anecdotally, most bugs happen: the interfaces between components, not the logic in the components themselves.

What might work better is tests for the external behaviour of the software, especially end to end tests. That might also be an integration test, depending on where you draw the component boundaries in your architecture (not necessarily at the class level!). These tests make it more straightforward to demonstrate that the system under test provides certain business value, for example that your REST API supports a collection lifecycle for clients of the API. Indirectly, this might also test internal details like the add() method raising an exception in illegal states. If an internal component is so complex to merit more detailed testing, writing unit tests to thoroughly exercise that component in isolation is possibly much more efficient and a good idea as well. So the idea would be to focus testing effort where it produces the most value, but not aiming for 100% unit test coverage.

That said, if unit testing is painful because lots of mocks, this doesn't necessarily mean that the tests aren't worth it – it can also mean that the design is not very testable. In particular, I find classic OOP style architectures to be pretty difficult to test because the object graph quickly gets out of control, whereas the data flows are often simpler in more functional designs.

2

Assuming "class with three methods" is the service that you talk about for RestController then I'll offer you the same answer as amon, just in a shorter form:

Tests must be useful. This is your prime measure of a test. So you don't want to test the controller and service separately as that isn't useful as it requires code duplication and just tests the same thing twice.

Test them together. Take your described test, modify it so you are "injecting [the mock] into the service, and injecting the service in the controller" and then test the result. Do that for all the tests you currently have for the service. Job done. That's that controller and service covered by tests. Now move on...

2
  • So you'd suggest a ControllerTest class, but no ServiceTest class, since the ControllerTest covers the services too?
    – Regyn
    Oct 8 '21 at 19:08
  • @Regyn that’s correct. If the tests in ControllerTests covers the functionality of the service then there’s no benefit to creating more tests for the service class
    – David Arno
    Oct 8 '21 at 20:03

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.