11

I have a class which is refactored in 1 main class and 2 smaller classes. The main classes use the database (like a lot of my classes do) and sends an email. So the main class has an IPersonRepository and an IEmailRepository injected which in his turn sends to the 2 smaller classes.

Now I want to unit test the main class, and have learned not to unittest the internal workings of the class, because we should be able to change the internal workings without breaking unit tests.

But as the class uses the IPersonRepository and an IEmailRepository, I HAVE to specify (mock / dummy) results for some methods for the IPersonRepository. The main class calculates some data based on existing data and returns that. If I want to test that, I don't see how I can write a test without specifying that the IPersonRepository.GetSavingsByCustomerId returns x. But then my unit test 'knows' about the internal workings, because it 'knows' which methods to mock and which not.

How can I test a class which has injected dependencies, without the test knowing about the internals?

background:

In my experience lots of tests like this create mocks for the repositories and then either provide the right data for the mocks or test if a specific method was called during execution. Either way, the test knows about the internals.

Now I've seen a presentation about the theory (which I've heard before) that the test should not know about the implementation. First because you are not testing how it works, but also because when you now change the implementation all unit tests fail because they 'know' about the implementation. While I like the concept of the tests being unaware of the implementation, I don't know how to accomplish it.

1
  • 1
    As soon as your main class expects an IPersonRepository object, that interface and all the methods it describes are not "internal" any more, so it is not really a problem of the test. Your real question should be "how can I refactor classes into smaller units without exposing too much in public". The answer is "keep those interfaces lean" (by sticking to the interface seggregation principle, for example). That is IMHO point 2 in @DavidArno's answer (I guess there is no need for me to repeat that in another answer).
    – Doc Brown
    Sep 5, 2016 at 15:31

3 Answers 3

7

The main class calculates some data based on existing data and returns that. If I want to test that, I don't see how I can write a test without specifying that the IPersonRepository.GetSavingsByCustomerId returns x. But then my unit test 'knows' about the internal workings, because it 'knows' which methods to mock and which not.

You are right that this is violation of the "do not test internals" principle and is a common one that people overlook.

How can I test a class which has injected dependencies, without the test knowing about the internals?

There are two solutions that you can adopt to work around this violation:

1) Provide a complete mock of IPersonRepository. Your currently described approach is to couple the mock to the inner workings of the method under test by only mocking the methods it will call. If you supply mocks for all the methods of IPersonRepository, then you remove that coupling. The inner workings can change without affecting the mock, thus making the test less brittle.

This approach has the advantage of keeping the DI mechanism simple, but it can create a lot of work if your interface defines many methods.

2) Do not inject IPersonRepository, either inject the GetSavingsByCustomerId method, or inject a savings value. The problem with injecting entire interface implementations is that you are then injecting ("tell, don't ask") an "ask, don't tell" system, mixing up the two approaches. If you take a "pure DI" approach, the method should be provided (told) the exact method to call if it wants a savings value, rather than being given an object (which it must then effectively ask for the method to call).

The advantage of this approach is that it avoids the need for mocks (beyond test methods that you inject into the method under test). The disadvantage is that it can cause method signatures to change in response to the requirements of the method changing.

Both approaches have their pros and cons, so pick the one that suits your needs best.

1
  • 1
    I really like idea number 2. I do not know why I haven't thought of that before. I've been creating dependencies on IRepositories for quite some years now without ever questioning myself why I created a dependency on a whole IRepository (which in lots of cases will contain quite a lot of method signatures) while it only has a dependency on one or 2 methods. So instead of injecting an IRepository I could better inject or pass along the (only)method signature needed.
    – Michel
    Sep 6, 2016 at 6:52
1

My Approach is to create 'mock' versions of the repositories which read from simple files which contain the needed data.

This means the individual test doesn't 'know' about the setup of the mock, although obviously the overall test project will reference the mock and have the setup files etc.

This avoids the complex mock object setup required by mocking frameworks and allows you to use the 'mock' objects in real instances of your application for UI testing and the like.

Because the 'mock' is fully implemented, rather than specifically setup for your test scenario, a change of implementation, for example lets say that GetSavingsForCustomer should now also delete the customer. Won't break the tests (unless of course it really breaks the tests) you need only update your single mock implementation and all your tests will run against it without changing their setup

2
  • 2
    This still leads to the situation that I create a mock with data for exactly the methods the tested class is working with. So I still have the issue that when the implementation is changed, the test will break.
    – Michel
    Sep 5, 2016 at 14:42
  • 1
    edited to explain why my approach is better, although still not perfect for the scenario i think you mean
    – Ewan
    Sep 5, 2016 at 14:53
1

Unit tests are usually whitebox-tests (you have access to the real code). Therefore it is ok to know internals to a certain extent, however for beginners it is easier to not, because you should not test internal behavior (such as "calling method a first, then b and then a again").

Injecting a mock that provides data is ok, as your class (unit) depends on that external data (or data-provider). However, you should verify the results (not the way to get to them)! e.g. you provide a Person instance and verify that an email was sent to the correct email-address, e.g. by providing a mock for the email, too, that mock does nothing but store the addressee's e-mail-address for later access by your test-code. (I think Martin Fowler calls them Stubs rather than Mocks, though)

Your Answer

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

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