0

I'm having doubts about if I should create tests that have many mock objects or not.

I recently read When should I mock? and I'm feeling confused.

Let's take a look at a method I have (it is just to illustrate the problem)

@Override
protected void validate() throws WTException {
    Either<ImportError, RootFinderResult> rootPart = getDataValidator().getRootPart();
    if (rootPart.isLeft()) {
        addValidationMessage(ROOT_PART_NOT_FOUND);
    } else if (rootPart.isRight()) {
        getObjectsToValidate().forEach(Lambda.uncheckedBiConsumer((part, epmDocuments) -> {
            LocalizableMessage rootRevision = getRevision(part);

            Optional<EPMDocument> wrongRevisionEPM = epmDocuments.stream()
                    .filter(epmDocument -> !isSameRevision(rootRevision, epmDocument))
                    .findAny();

            wrongRevisionEPM.ifPresent(epmDocument -> addValidationMessage("blabla"));
        }));
    }
}

All of the following methods need to have a connection to a server in order to work, otherwise they will throw errors

getDataValidator().getRootPart();
getRevision(part)
!isSameRevision(rootRevision, epmDocument))

In addition I can't create 'real' objects of part or epm documents. This also requires to have a connection to a server.


So at this point, what I really want to test is actually logic of this part of code

    Optional<EPMDocument> wrongRevisionEPM = epmDocuments.stream()
            .filter(epmDocument -> !isSameRevision(rootRevision, epmDocument))
            .findAny();

    wrongRevisionEPM.ifPresent(epmDocument -> addValidationMessage("blabla"));

But to test it I need to mock really many objects

@Spy
@InjectMocks
private SameRevision sameRevision;
@Mock
private WTPartRelatedObjectDataValidator wTPartRelatedObjectDataValidator;
@Mock
private ValidationEntry validationEntry;
@Mock
private WTPart rootPart1, rootPart2;
@Mock
private EPMDocument epmDocument1, epmDocument2, epmDocument3;
@Mock
private Either<ImportError, RootFinderResult> rootPart;
@Mock
private LocalizableMessage rootPartRevisionOne, rootPartRevisionTwo;

so finally I can test the logic:

@Test
@DisplayName("Should contain error message when part -> epms revisions are not the same")
void shoulHaveErrorMessagesWhenDifferentRevisions() throws Exception {
    doReturn(getMockObjectsToValidate()).when(sameRevision).getObjectsToValidate();

    doReturn(rootPart).when(liebherrWTPartRelatedObjectDataValidator).getRootPart();
    doReturn(false).when(rootPart).isLeft();
    doReturn(true).when(rootPart).isRight();

    doReturn(rootPartRevisionOne).when(sameRevision).getRevision(rootPart1);
    doReturn(rootPartRevisionTwo).when(sameRevision).getRevision(rootPart2);

    doReturn(true).when(sameRevision).isSameRevision(rootPartRevisionOne, epmDocument1);
    doReturn(false).when(sameRevision).isSameRevision(rootPartRevisionOne, epmDocument2);
    doReturn(true).when(sameRevision).isSameRevision(rootPartRevisionTwo, epmDocument3);

    validationEntry = sameRevision.call();

    assertEquals(1, validationEntry.getValidationMessageSet().size());
}

where

    doReturn(rootPart).when(liebherrWTPartRelatedObjectDataValidator).getRootPart();
    doReturn(false).when(rootPart).isLeft();
    doReturn(true).when(rootPart).isRight();

    doReturn(rootPartRevisionOne).when(sameRevision).getRevision(rootPart1);
    doReturn(rootPartRevisionTwo).when(sameRevision).getRevision(rootPart2);

can be moved to @BeforeEach.


At last, I have my test and it works. It validates what I wanted to be validated but in order to come to this point I had to put a lot of effort to come through the whole API which needs interactions with a server.

What do you guys think, is it worth it to create tests like this? I guess this is a wide-open topic 'cause many newbies that try to come into the 'test world' will have similar a problem, so please do not close the topic because of opinion-based judgement and give your feedback on this topic.

tom1299
  • 61
  • 9
Aramka
  • 91
  • 2
  • 9

3 Answers3

1

You should mock other dependencies on which your class which going to be tested relies and set up behavior which you need . This needs to be done to test your method isolated and not dependent on thrirdparty classes You can write private void methods which can contain your mock behavior and use them in tests , In @BeforeEach annotated method you could mock behavior which wil be same in all tests or mock the same mocking behavior for across all tests

In your method which is void you can have spy objects which can be veereified if they were called like Mockito.verify()

Mykhailo Moskura
  • 1,549
  • 1
  • 4
  • 15
1

You are right. It is a big effort to mock all those dependencies. Let me get over a few points that may make things clearer:

  • Treat writing tests like an investment: So yes, sometimes it is more effort to write the test than to write the actual code. However, you will thank yourself later when you introduce a bug and the tests can catch it. Having good tests gives you confidence when modifying your code that you didn't break anything, and if you did, your tests will find the issue. It pays off over time.

  • Keep your test focused on a specific class. Mock the rest: When you mock everything except the class under test, you can be sure that when a problem occurs that it is from the class under test, and not from one of its dependencies. This makes troubleshooting a lot easier.

  • Think of testability when writing new code: Sometimes it may not be avoidable to have a complicated piece of code which is hard to test. However, generally, this situation can be avoided by keeping the number of dependencies you have to a minimum and writing testable code. For example, if a method needs 5 or 6 more dependencies to do its job, then probably that method is doing too much and could be broken down. Same thing can be said on a class level, modules, etc..

Nullbeans
  • 300
  • 1
  • 8
0

Yes, it is quite time investing when you have to mock so many things. In my opinion, if you add some value when testing something, it is worth testing, the problem could be of course how much time would you consume.

In your specific case, I would test on different "layers".

For example, the methods: getDataValidator().getRootPart(); getRevision(part) !isSameRevision(rootRevision, epmDocument))

They can be independently tested and in your case just mock their result, meaning that you don't really care about the parameters there, you just care what happens in case of a certain return value.

So, on one layer you really test the functionality, on the next layer you just mock the result you need, in order to test the other functionality.

I hope it is more clear now...

Andrea Calin
  • 288
  • 2
  • 10