13

In the advice of Mockito about how to write good tests, it is written that we should not mock value objects (https://github.com/mockito/mockito/wiki/How-to-write-good-tests#dont-mock-value-objects). They even say

Because instantiating the object is too painful !? => not a valid reason.

But I don't understand. What if we really don't care about what that object contain? Let's just say that I have a value object class "Foo" that doesn't have simple constructors (it has an all-args constructor and contains objects that also need all-args constructors). Let's also say I have the following service:

public class MyService {
    private SomeService someService;
    private OtherService otherService;

    public void myAwesomeMethod() {
        Foo foo = someService.getFoo();
        someOtherService.doStuff(foo);
    }
}

And I need to test that method. Clearly here I don't care about what foo contains because I just pass it to another method. If I want to test it should I really create a foo object with nested constructors/builders? For me it would be really easy to mock foo even if it is a POJO :

public class MyServiceTest {
    @InjectMocks
    MyService myService;

    @Mock
    private SomeService someService;

    @Mock
    private OtherService otherService;

    @Test
    public void testMyAwesomeMethod() {
        Foo mockedFoo = Mockito.mock(Foo.class);

        Mockito.when(someService.getFoo()).thenReturn(mockedFoo);

        Mockito.verify(otherService).doStuff(mockedFoo);
    }

}

But then it wouldn't respect the guidelines given by Mockito. Is there another alternative?

Ricola
  • 269
  • 1
  • 3
  • 7
  • 5
    The paragraph of documentation that you partially quoted goes on to suggest a good solution: _"An alternative is to create builders for your value objects [...] One can also create meaningful factory methods in the test classpath."_. – jscs Dec 29 '17 at 14:46
  • What's wrong with `Foo testFoo = new Foo()` or `Foo testFoo = buildFoo()`? Your tests would execute a bit faster since you aren't micro-mocking. – Berin Loritsch Dec 29 '17 at 15:45
  • new Foo() is not possible because I wrote "Let's just say that I have a value object class "Foo" that doesn't have simple constructors (it has an all-args constructor and contains objects that also need all-args constructors)." Like an immutable object with final fields. I could create a builder that fills the whole object and its fields with a lot of dummy values, and do '"buildFoo()", but what's the point of creating a whole builder for that test if I don't need any of those fields? If I don't need that builder in other tests it's just more code and more time for no added value. – Ricola Dec 29 '17 at 16:09
  • 1
    _"for no added value"_ If the test doesn't provide enough value to justify the work involved in writing it, then don't write the test. – jscs Dec 29 '17 at 16:17
  • When I said "for no added value" I mean that filling the object with dummy data adds no value since we just have to test if only its reference is passed to that other service. – Ricola Dec 29 '17 at 19:16
  • 1
    Such test doesn't provide any value to the project from the quality standpoint (IMO). Looking at the "production code" it's obvious that the test will pass because `myAwesomeMethod` does nothing. A good test would be an error case. For instance, what happens if `someService` or `otherService` fails. `myAwesomeMethod` is totally opaque for any client. It has nothing that worth to be tested but errors or exceptions. – Laiv Dec 29 '17 at 21:07
  • You need something like JFixture – Adrian Iftode Dec 30 '17 at 11:43
  • https://github.com/FlexTradeUKLtd/jfixture – Adrian Iftode Dec 30 '17 at 11:44
  • Or maybe this also helps https://github.com/benas/random-beans – Adrian Iftode Dec 30 '17 at 13:01
  • 1
    It seems to me that your unit test is actually testing nothing. You create a mock, you then "program" a mock and finally you verify it. But there isn't any actual invocation! Am I missing something? – danidemi Jan 12 '18 at 21:45

4 Answers4

14

Stubs (that you've called mocks in your question) exist for a reason: they make it possible to replace business code from the classes you depend on in the method you're testing by some very basic statements which provide enough data for the tested method. Mocks, just like stubs, replace business logic by custom logic required for the tests.

  • If a POJO doesn't have business code (i.e. if it's also a data object), stubs and mocks would be useless. Using such data objects in tests should be quite easy. If the method needs a data object in input, just create one in the test.

    Some data objects may be very large, containing dozens of properties. In some languages, you can partially initialize a data object by setting only the properties you need during the test; in other languages, you need to fully initialize it. If this is your case, creating a simple static method which creates the data object with the default values and reusing this method all over the tests could be a solution.

  • If a POJO contains business code, you should isolate your unit tests from it, like you would do for any other class. Here, stubs and mocks are fine, and it doesn't make a difference if the mocked object is a POJO or something else.

Arseni Mourzenko
  • 134,780
  • 31
  • 343
  • 513
  • While a value object might be a POJO a POJO doesn't have to be a value object. POJO's are perfectly free to have business rule code. – candied_orange Dec 30 '17 at 00:35
  • @CandiedOrange: Then what distinguishes a POJO from a non-POJO? – Robert Harvey Dec 30 '17 at 04:05
  • @RobertHarvey A [POJO](https://en.m.wikipedia.org/wiki/Plain_old_Java_object) has no prespecified interfaces, inheritance, or annotations. You can't look at it and tell what framework it depends on because it doesn't depend on a framework. No one said it couldn't have business rules. – candied_orange Dec 30 '17 at 04:18
  • @CandiedOrange: Oh, so an ordinary, unremarkable Java class then, and not a Bean, Servlet or other such abominations. – Robert Harvey Dec 30 '17 at 04:41
  • @RobertHarvey exactly. Sadly there isn't much money in promoting simple things that just work. – candied_orange Dec 30 '17 at 04:43
  • @CandiedOrange: that's interesting; the definition of a POJO has nothing to do with what I thought a POJO is. Thank you, I learned something new today. – Arseni Mourzenko Dec 30 '17 at 10:06
4

The best line from your link in my opinion is

Don't mock everything, it's an anti-pattern

If everything is mocked, are we really testing the production code? Don't hesitate to not mock!

Usually you want to test your code with real inputs and expected outputs and usually these will be data structures or pojos/pocos

ie.

actual = myfunc(input)
Assert.AreEqual(actual, expected)

It doesn't make sense to mock that input or output because its set up specifically for that test. You always care about it.

In your example you have no input, output or indeed logic to perform. Even if you have a real example where code just passes an object from one service to another, surely you want to test edge cases like nulls or a very large object? You should care about what that object is.

Ewan
  • 70,664
  • 5
  • 76
  • 161
2

You might be running into a code smell.

Clearly here I don't care about what foo contains because I just pass it to another method.

The thing that stands out to me in this example is that your code doesn't even care that the object being exchanged is a Foo.

public class MyService<T> {
    private java.util.function.Supplier<? extends T> someService;
    private java.util.function.Consumer<? super T> otherService;

    public void myAwesomeMethod() {
        T foo = someService.get();
        someOtherService.accept(foo);
    }
}

Then, in your unit test, instead of trying to use a Foo, or to create a test double that you can use in place of a Foo, you can inject a Supplier/Consumer pair.

Let's just say that I have a value object class "Foo" that doesn't have simple constructors (it has an all-args constructor and contains objects that also need all-args constructors).

Another common pattern is to use test data builders to create the values that you need. Test data builders typically use fluent interfaces to communicate within a test which details of the object are important in the context of the test.

@Test
public void testMyAwesomeMethod() {
    Foo mockedFoo = new FooBuilder().anyOldFoo();

    Mockito.when(someService.getFoo()).thenReturn(mockedFoo);

    Mockito.verify(otherService).doStuff(mockedFoo);
}

Here, the interaction with the builder articulates that the behavior of this test shouldn't depend on the specific foo value.

If you squint, you might see that the test is asserting a property

public void testMyAwesomeMethod() {
    for (Foo candidate : allPossibleFoo()) {
        Mockito.when(someService.getFoo()).thenReturn(candidate);
        // ...
    }
}

None of these builder approaches directly solves the problem of invoking the fool constructor; all the builder does is move that complexity out of the test.

VoiceOfUnreason
  • 32,131
  • 2
  • 42
  • 79
1

You are somewhat correct.

When you mock an object, you typically stub out the methods to return canned values instead. But there isn't really a good reason to do this for a value object, just use the value object instead.

However, in this particular scenario you aren't stubbing out any of the methods. You are just verifying that the same object returned from one function gets passed to another. So you don't get the disadvantages of mocking the value object.

Nevertheless, you almost certainly should have test code that constructs a Foo. The test for SomeService should probably assertEquals the result of getFoo with some constructed Foo. The test for OtherService should pass some constructed Foo object to doStuff. You've got to have code somewhere that constructs Foo objects for those tests, so you should be able to readily refactor that construction code into a place where it can be reused.

However, in general, it is suspicious that MyService doesn't want to call any methods on Foo. This is often a sign that MyService shouldn't be dealing with Foo at all. If all MyService does it take an object from SomeService and pass it to OtherService what's the point of MyService? Simplify your code and connect the two services directly.

Winston Ewert
  • 24,732
  • 12
  • 72
  • 103