java Mockito - 注入模拟列表

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/42351117/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-03 06:33:38  来源:igfitidea点击:

Mockito - Injecting a List of mocks

javaspringjunitdependency-injectionmockito

提问by fascynacja

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:

我想使用 Mockito 为它创建一个测试。我写的测试如下:

@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:

我在线收到 NullPointerException:

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?

,这表明“策略”列表已初始化但为空。Mohito 有什么办法可以像 Spring 一样表现得一样吗?自动将实现接口“策略”的所有实例添加到列表中?

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

顺便说一句,我在 Wrapper 类中没有任何二传手,如果可能的话,我想以这种方式保留它。

采纳答案by thopaw

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

Mockito 无法知道您想在 List策略中放入一些东西。

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);
}

回答by Erwin Dupont

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.

使用 @Spy 而不是 @Mock 对其进行注释。由于 Mockito 无法监视接口,因此请使用具体实现,例如 ArrayList。在测试设置期间,将模拟添加到 List 间谍。这样你就不需要仅仅为了测试目的而改变你的测试对象。

@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);
}

回答by Naruto Sempai

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

为什么不模拟你对 的调用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.

对我来说,这要优雅得多,因为它不需要您添加仅与代码测试相关的辅助方法。

回答by Timothy Truckle

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);
}

回答by Andrew Spencer

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.

Erwin Dupont 的解决方案很好,但当您需要在测试对象的构造函数中注入模拟列表时,它不起作用。

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:

这就是我如何解决这个问题的。我已经展示了列表中仅 1 个项目的解决方案,但您可以通过将 aswitch(index)放入get()方法将其扩展到 N 个项目:

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);
  }
}