Reputation: 2858
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.
Upvotes: 36
Views: 33740
Reputation: 279
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);
}
Upvotes: 0
Reputation: 16504
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);
}
}
Upvotes: 1
Reputation: 577
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);
}
Upvotes: 42
Reputation: 7201
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.
Upvotes: 1
Reputation: 4054
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);
}
Upvotes: 8
Reputation: 15622
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);
}
Upvotes: 2