【问题标题】:Mockito - Injecting a List of mocksMockito - 注入模拟列表
【发布时间】:2017-02-20 17:44:08
【问题描述】:

我有以下代码:

@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{...}

我想使用 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);
}

我在线收到 NullPointerException:

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

,它表示“策略”列表已初始化但为空。 Mohito 有没有办法像 Spring 一样表现得一样?将所有实现接口“策略”的实例自动添加到列表中?

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

【问题讨论】:

    标签: java spring junit dependency-injection mockito


    【解决方案1】:

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

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

    【讨论】:

    • 谢谢,具体 impl 的解释正是我仍然缺少的拼图的一部分......我尝试了 @Mock 和调用真实方法的答案,但后来它们没有被添加!跨度>
    • 有用,但项目被添加到列表之后列表被注入到目标对象。因此,如果您制作列表内容的防御性副本,此方法将失败。
    • 如果你确实采取了这条路线,你不想将testedObject设置为new Wrapper(),因为@InjectMocks注解告诉Mockito为你实例化它;模拟列表也可以是一个简单的List&lt;&gt;(不需要是具体的类ArrayList&lt;&gt;
    • 实际上我尝试了@Spy List<...>,但没有成功。它必须是ArrayList&lt;...&gt;。我认为这是因为 spy 会调用 List 上真正的add 方法,它不会像接口中的列表那样做任何事情。
    • 其他可行的方式:@Spy private List&lt;Strategy&gt; mockedStrategies = new ArrayList&lt;&gt;();
    【解决方案2】:

    Mockito 无法知道您想将某些东西放入列表中策略

    你应该重新考虑这个并做这样的事情

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

    【讨论】:

    • Thomas,Spring 不知何故知道.. 所以我想知道 Mockito 是否可以遵循相同的想法。除此之外,我不想将“setStrategies”设置器添加到 Wrapper 类。
    • Spring 通过告诉(应用程序上下文)或通过约定(例如类型)知道。但我很确定 Spring 也无法连接通用列表。类型信息在运行时也不可用,因为由于类型擦除阶段,编译器不会将其写入字节码。所以我想在没有告诉/编程的情况下创建一个包含模拟的模拟列表是不可能的
    • spring 可以连接示例“Wrapper”类,注入 strategyA 和 strategyB bean。应用程序上下文仅基于注释构建,所以我希望 Mockito 也能做到。
    【解决方案3】:

    你不应该模拟集合。

    创建您需要的模拟并将它们放入列表中:

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

    【讨论】:

    • 蒂莫西,我希望得到一个解决方案,我不必将“策略”列表明确设置为“测试对象”
    【解决方案4】:

    为什么不直接模拟您对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));
    }
    

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

    【讨论】:

      【解决方案5】:

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

      这就是我解决这个问题的方法。我只展示了列表中一项的解决方案,但您可以通过将 switch(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);
        }
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-05-28
        • 2017-10-11
        • 1970-01-01
        • 2017-05-25
        • 1970-01-01
        相关资源
        最近更新 更多