0

I have a service method, acceptOrDenyJoinRequest, which follows a fairly complex flow (as depicted in this diagram):

Image to depict an endpoint

In my unit tests, the implementation details of this method are heavily reflected. The process involves numerous interactions with the database (represented by the rectangle boxes in the diagram), as well as some subprocesses. Because of this, I end up mocking all these interactions for every test case. In each test, I slightly modify the mock values depending on the specific scenario being tested.

The main challenges I face are:

  1. Excessive Mocking: There are too many dependencies to mock, and in some cases, I spend more time configuring the mocks than writing the actual test logic.
  2. Tight Coupling with Implementation: The tests are tightly coupled with the internal workings of the method. This means that when I refactor the code, I also have to refactor the unit tests because they are so closely tied to the implementation.
  3. No Return Values: The method itself doesn’t return anything, so the only way to verify its correctness is by asserting that the right database calls were made with the appropriate values.
  4. Integration Tests Overlap: The same scenarios that I cover in unit tests are also covered by my integration tests, but without mocks. This makes me question whether the unit tests are adding value since both sets of tests cover the same paths.

My questions are:

  1. How can I avoid excessive mocking and reduce the tight coupling between the tests and the implementation?
  2. Is there a better approach to testing methods with many database interactions, where verifying the database calls is necessary but doesn’t lead to fragile tests that break on refactor?
  3. Should I reconsider the value of unit tests when integration tests already cover the same scenarios without mocks?

Any advice or best practices for writing maintainable unit tests, especially for database-heavy methods, would be greatly appreciated!

1
  • What kind of "integration" tests do you mean. The slow-running real-database running kind? Or fast, reliable in-memory-database kind?
    – Euphoric
    Commented Sep 5 at 7:23

1 Answer 1

5

How can I avoid excessive mocking and reduce the tight coupling between the tests and the implementation?

Don't mock things. Or only mock things you cannot otherwise test.

If your service requires a database, give it a database. In this case, maybe an in-memory database that is created for this test and destroyed after this test.

The more you mock, the more you are actually only testing whether you mocked correctly. And that is a totally irrelevant measurement. You are testing to see if it runs correctly if not mocked. That is the goal.

Because now you need additional unit tests that test what you mocked out here. The database calls. Maybe your framework or OR mapper or self-built SQL, but either way, that is currently untested. You have not tested if your code fulfills the requirements, you have tested whether your mock works.

Is there a better approach to testing methods with many database interactions, where verifying the database calls is necessary but doesn’t lead to fragile tests that break on refactor?

Well, even using an actual database will not protect you from breaking tests, if you change the methods requirements. But if you are only changing internal behaviour, if you don't mock internals, but use the actual internals you use in production, you never need to adjust your mocks.

Should I reconsider the value of unit tests when integration tests already cover the same scenarios without mocks?

Depends on what you mean by "integration test". There is no point in testing private methods. You test the public interface. If you insist on testing every method by mocking all it's dependencies and call that the only correct unit testing, then well... I will not argue with you there... that is your mess to handle.

In my view, unit testing should test reasonable units of your code. That you can setup in one test, tear down after and run in memory reasonably fast.

A service that has a dependency on a repository class that has a dependency on the database to me is one test (multiple tests obviously for multiple requirements, error conditions and edge cases, but not one for the service with a mocked repository and one for the repository with a mocked database). You put it all together and test whether your requirement works. Except for an in-memory database, you have zero need for mocks. Test your actual code for production, not your tons and tons of mocks you would need if you go down that path.

2
  • So if I don't mock and use database, it is still called unit test? Commented Sep 5 at 10:05
  • 3
    I don't know. I do it all the time, the project is of type unit test, the framework I use is a unit testing framework... so far nobody dragged me in front of the official testing naming court. Call it what you want, as long as it produces the results you need.
    – nvoigt
    Commented Sep 5 at 12:10

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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