37

I have the following code:

@Component 
public class Wrapper
{ 
    @Resource 
    private List<Strategy> strategies;

    public String getName(String id)
    {
    // the revelant part of this statement is that I would like to iterate over "strategies"
        return strategies.stream()
            .filter(strategy -> strategy.isApplicable(id))
            .findFirst().get().getAmount(id);
    } 
}

@Component 
public class StrategyA implements Strategy{...}

@Component 
public class StrategyB implements Strategy{...}

I would like to create a Test for it using Mockito. I wrote the test as follows:

@InjectMocks
private Wrapper testedObject = new Wrapper ();

// I was hoping that this list will contain both strategies: strategyA and strategyB
@Mock
private List<Strategy> strategies;

@Mock
StrategyA strategyA;

@Mock
StrategyB strategyB;

@Test
public void shouldReturnNameForGivenId()
{   // irrevelant code...
    //when
    testedObject.getName(ID);
}

I am getting NullPointerException on line:

filter(strategy -> strategy.isApplicable(id))

, which states that the "strategies" list is initialized but is empty. Is there any way Mohito will behave in the same wasy as Spring? Adding automatically all instances implementing interface "Strategy" to the list?

Btw I do not have any setters in Wrapper class and I would like to leave it in that way if possible.

6 Answers 6

43

Annotate it with @Spy instead of @Mock. As Mockito cannot spy on an interface, use a concrete implementation, for example ArrayList. During test setup add the mocks to the List spy. This way you do not need to alter your test subject solely for test purposes.

@InjectMocks
private Wrapper testedObject = new Wrapper();

@Spy
private ArrayList<Strategy> mockedStrategies;

@Mock
private StrategyA strategyA;

@Mock
private StrategyB strategyB;

@Before
public void setup() throws Exception {
    mockedStrategies.add(strategyA);
    mockedStrategies.add(strategyB);
}
Sign up to request clarification or add additional context in comments.

5 Comments

Thanks man, the explanation of the concrete impl was exactly the piece of the puzzle I was still missing... I tried @Mock with call real methods answer, but then they are not added!
Useful, but the items get added to the list after the list is injected to the target object. So, if you make a defensive copy of the list contents, this method fails.
If you do take this route, you don't want to set testedObject to new Wrapper() because the @InjectMocks annotation tells Mockito to instantiate it for you; also the mocked list can be a simple List<> (does not need to be the concrete class ArrayList<>)
Actually I tried to have @Spy List<...> and it didn't work. It MUST be an ArrayList<...>. I think it's because spy will call the real add method on List, which will do nothing as list in an interface.
Other way that works: @Spy private List<Strategy> mockedStrategies = new ArrayList<>();
8

Mockito can not know that you want to put somthing in the List strategies.

You should rethink this an do something like this

@InjectMocks
private Wrapper testedObject = new Wrapper ();

private List<Strategy> mockedStrategies;

@Mock
StrategyA strategyA;

@Mock
StrategyB strategyB;

@Before
public void setup() throws Exception {
    mockedStrategies = Arrays.asList(strategyA, strategyB);
    wrapper.setStrategies(mockedStrategies);
}

3 Comments

Thomas, Spring somehow knows.. So I was wondering if Mockito could follow the same idea. Besides that, I do not want to add "setStrategies" setter to the Wrapper class.
Spring knows either by telling (application context) or by convention (e.g. Types). But I am pretty sure that Spring also can not wire Generic Lists. The Type information are also not available during runtime as the compiler don't write it in the bytecode because of the typre erasure phase. So I guess it is not possible to create a mocked list containing mocks without telling/programming
spring can wire the example "Wrapper" class, injecting the strategyA and strategyB beans. Application context is build only based on annotations, so I hoped Mockito can do the same.
2

You should not mock collections.

Create the mocks you need and put them into a list:

private List<Strategy> strategies; // not mocked!

@Mock
StrategyA strategyA;

@Mock
StrategyB strategyB;

@Before
public void setup(){
  strategies= Arrays.asList(strategyA,strategyB);
  testedObject.strategies= strategies;
}

@Test
public void shouldReturnNameForGivenId()
{   // irrevelant code...
    //when
    testedObject.getName(ID);
}

1 Comment

Timothy, I was hoping to get a solution in which I do not have to set the "strategies" list explicitly to the "testedObject"
1

Why not just mock out your call to toStream()?

@InjectMocks
private Wrapper testedObject = new Wrapper();

private List<Strategy> mockedStrategies;

@Mock
StrategyA strategyA;

@Mock
StrategyB strategyB;

@Before
public void setup() {
    when(strategies.stream()).thenReturn(Stream.of(strategyA, strategyB));
}

To me this is far more elegant as it doesn't require you to add a helper method that is only relevant to testing to your code.

Comments

1

The solution from Erwin Dupont is nice but does not work when you need to inject the List of mocks in the constructor of the tested object.

Here's how I got round that. I've shown the solution for just 1 item in the list, but you could extend it to N items by putting a switch(index) into the get() method:

class Wrapper {
  private final List<Strategy> strategies;
  Wrapper(List<Strategy> strategies) { this.strategies = new ArrayList<>(strategies); }
  // ...
}

class WrapperTest {
  @InjectMocks
  private Wrapper testedObject;

  @Spy
  private List<Strategy> mockedStrategies new AbstractList<Strategy>() {
      @Override public Trigger get(int index) { return trigger; } // can get away without bounds-checking
      @Override public int size() { return 1; }
  };

  @Mock
  private Strategy strategy;

  @Test
  public void testSomething() {
    assertThat(testedObject).isNotNull();
    assertThat(testedObject.getStrategies()).hasSize(1);
  }
}

Comments

1

I solved this problem by using ReflectionUtils.

The only thing I don't like is the member that you are setting cannot be final.

@InjectMocks
private Wrapper testedObject = new Wrapper ();


@Mock
StrategyA strategyA;

@Mock
StrategyB strategyB;

@BeforeEach
public void setup() throws IllegalAccessException {
    MockitoAnnotations.openMocks(this);

    var stratsInMock =
      ReflectionUtils.findFields(Wrapper.class, f -> f.getName().equals("stratigies"), ReflectionUtils.HierarchyTraversalMode.TOP_DOWN).get(0);
    stratsInMock.setAccessible(true);
    var aList = List.of(strategyA, strategyB);
    stratsInMock.set(wrapper, aList);
}

@Test
public void shouldReturnNameForGivenId()
{   // irrevelant code...
    //when
    testedObject.getName(ID);
}

Comments

Your Answer

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.