Dexters
Dexters

Reputation: 2495

Adding a mock object to a Mockito spy List<>

Cant a Mockito Spy List object contains other Mock objects added to it? Any alternatives other than creating actual objects?

I have a Spy List Object

Class TestableClassTest {
@InjectMocks
TestableClass myClassUnderTest;

@Mock
MyService myService
@Spy
List<MyBusinessClass> myBusinessClasses;
@Mock 
MyBusinessClass myBusinessClass1;
@Mock
MyBusinessClass myBusinessClass2;

ResponseEntity result;

@Rule
MockitoRule rule = MockitoJunit.rule();

@Before
public void setup()
{
  myBusinessClasses.add(myBusinessClass1);
  myBusinessClasses.add(myBusinessClass2);
  when(myService.get()).thenReturn(myBusinessClasses);
  result = myClassUnderTest.testThisMethod();
}
 @Test
  public void resultIsReceivedWithNoException()
  { Assert.assertNotNull(result);}
}

But this returns nullpointer exception when doing this

List<MyBusinessClass> list = someService.get(); MyBusinessClass 
myBusinessClass = list.get(0);// this is null

It works only if I create real Object myBusinessClass1 and myBusinessClass2 from MyBusinessClass and then add it to the spy() list. What I mean by this is I dont get null any more if I create the objects with MyBusinessClass myBusinessClass1 = new MyBusinessClass() and then add it to the list.

Edit: So my questions when Unit testing a class where we return a list from a mocked Class' get() method. How can I put in some real concrete data into that List so that my Class Method which I am testing can work on that data and then I can test that my method works correctly on the data.

Upvotes: 1

Views: 4225

Answers (3)

Timothy Truckle
Timothy Truckle

Reputation: 15634

My previous lead used to say nothing should be a real object

In that case you would also mock (and not spy) the collection at the cost of having to specify its simple behavior:

@Mock
MyService myService
@Mock
List<MyBusinessClass> myBusinessClasses;
@Mock 
MyBusinessClass myBusinessClass1;
@Mock
MyBusinessClass myBusinessClass2;

@Before
public void setup()
{
myBusinessClasses.add(myBusinessClass1);
myBusinessClasses.add(myBusinessClass2);

when(myBusinessClasses.size()).thenReturn(2);
when(myBusinessClasses.get(0)).thenReturn(myBusinessClass1);
when(myBusinessClasses.get(1)).thenReturn(myBusinessClass2);

when(myService.get()).thenReturn(myBusinessClasses);
}

@Test
public void callSomeMethodOnReturnedEntries(){
    new CodeUnderTest(myService).publicInterfaceMethod();

    verify(myBusinessClass1).expectedMethodCall();
    verify(myBusinessClass2).expectedMethodCall();
}

I for myself would call tat overkill.

off topic

This would make a bad unittest since it relies on some implementation detail:
the way your production code accesses the elements in that list.

If you decide to change this implementation detail to, lets say, using an iterator of the list or the collections stream API, you would have to change this unittest to adopt the new implementation.

The real bad thing here is that in this case this test can not prove that your refactoring of changing the element access did not change the desired business behavior.

Upvotes: 1

GhostCat
GhostCat

Reputation: 140633

This here:

@Spy
List<MyBusinessClass> myBusinessClasses;

Really doesn't make any sense. A spy enables partial mocking of the "spied" object. So you could call "real" methods sometimes, and prevent others from being called. See here for guidance why/how to use a Mockito spy.

But you really shouldn't do that for List objects. Lists are just containers. When you have a unit test, and you have to control a List instance, you do that by simply putting the needed objects into that List. You don't need a mocked or spy List. You just create some list instance, and put the objects into it that you want it to contain.

So the real point is: you have to ensure how you can get someService.get(); to return that list with known content. But we can't help with that part, until you update your question accordingly.

Upvotes: 2

Timothy Truckle
Timothy Truckle

Reputation: 15634

So my questions when Unit testing a class where we return a list from a mocked Class' get() method. How can I put in some real concrete data into that List so that my Class Method which I am testing can work on that data and then I can test that my method works correctly on the data.

Given that MyBusinessClass is a real dependency providing some business logic which has its own unittests in place the approach would be like this:

@Mock
MyService myService

List<MyBusinessClass> myBusinessClasses = new ArrayList<>();
@Mock 
MyBusinessClass myBusinessClass1;
@Mock
MyBusinessClass myBusinessClass2;

@Before
public void setup()
{
myBusinessClasses.add(myBusinessClass1);
myBusinessClasses.add(myBusinessClass2);
when(myService.get()).thenReturn(myBusinessClasses);
}

@Test
public void callSomeMethodOnReturnedEntries(){
    new CodeUnderTest(myService).publicInterfaceMethod();

    verify(myBusinessClass1).expectedMethodCall();
    verify(myBusinessClass2).expectedMethodCall();
}

Upvotes: 3

Related Questions