【问题标题】:Mocking a List and attempting to iterate over it模拟列表并尝试对其进行迭代
【发布时间】:2017-08-21 09:13:27
【问题描述】:

目前正在使用 Mockito 测试我的一个类中的方法。我的类包含一个列表,该方法接受同一类的对象。问题是当我尝试从对象迭代列表时。我得到一个指向列表的空指针。下面你会看到代码sn-ps。

private Shipment shipment;
private Shipment shipment2;
@Mock
private Order mockOrder1;
@Mock
private Order mockOrder2;
@Mock
private Order mockOrder3;
@Mock
private ArrayList<Order> mockShipmentOrders;
@Mock
private ArrayList<Order> mockShipmentOrders2;

@Before
public void setUp(){
    MockitoAnnotations.initMocks(this);
    mockShipmentOrders.add(mockOrder1);
    mockShipmentOrders.add(mockOrder2);
    mockShipmentOrders2.add(mockOrder3);
    shipment = new Shipment(1, mockShipmentOrders);
    shipment2 = new Shipment(2, mockShipmentOrders2);
}

@Test
public void test_mergeShipments_increasesByOneWhenAShipmentOfOneAddedToAShipmentORderSizeOfTwo(){
    shipment.mergeShipments(shipment2);
    assertEquals(3, shipment.getShipmentOrders().size());
}

上面你可以看到我的 mockito 测试,下面是我的 Class 方法:

公共类发货{

private long shipmentID;
private List<Order> shipmentOrders;

public Shipment(long shipmentID, List<Order> shipmentOrders){
    this.shipmentID = shipmentID;
    this.shipmentOrders = shipmentOrders;
}

public List<Order> getShipmentOrders(){
    return shipmentOrders;
}

public void mergeShipments(Shipment shipment2){     
    List<Order> existingShipment = shipment2.getShipmentOrders();
    for (Order order : existingShipment){
        shipmentOrders.add(order);
    }
}

当我运行测试时,我得到了以下行的 java.lang.NullPointerException:for (Order order : existingShipment){ 在mergeShipemts();

问题是;是否可以模拟一个列表,调用该列表,然后在该模拟列表上运行 foreach?

【问题讨论】:

  • 为什么需要模拟一个列表?

标签: java list loops testing mocking


【解决方案1】:

有一些基本问题导致您的示例不起作用并引发NullPointerException

  1. 对模拟列表上的add() 的调用实际上没有任何作用。默认情况下,模拟上的所有 void 方法都是“无操作”
  2. 使用 for-each 语法在列表中迭代,在后台调用 Collection.iterator()。这将返回 null,因为您尚未设置 mockito 来返回任何其他内容。

相反,我不会模拟列表,而是传递一个实际列表。 Arrays.asList() 方便测试。

@Before
public void setUp(){
    MockitoAnnotations.initMocks(this);
    shipment = new Shipment(1, Arrays.asList(mockOrder1, mockOrder2));
    shipment2 = new Shipment(2, Arrays.asList(mockOrder3));
}

如果你决定模拟一个列表,那么你将不得不模拟它的行为,即让 add() 实际上存储一些东西,而 .iterator() 返回一个迭代器。这可以相当痛苦地完成,如下所示。我只是为了演示原理而将其包含在内。

@Mock
private List<String> mockedList;

@Before
public void init() {
    MockitoAnnotations.initMocks(this);

    List<String> realList = new ArrayList<>();
    doAnswer(new Answer<String>() {
        @Override
        public String answer(InvocationOnMock invocation) throws Throwable {
            realList.add(invocation.getArgumentAt(0, String.class));
            return null;
        }

    }).when(mockedList).add(any());

    when(mockedList.iterator()).thenAnswer(new Answer<Iterator<String>>() {

        @Override
        public Iterator<String> answer(InvocationOnMock invocation) throws Throwable {
            return realList.iterator();
        }
    });

    mockedList.add("bar");
    mockedList.add("baz");
}

@Test
public void iterateOverMockedList() {
    for (String each : mockedList) {
        System.out.println(each);
    }
}

【讨论】:

  • 感谢您的快速回复。我意识到模拟 List 相当困难,而我对 Mockito 的理解是“模拟”一切。我现在已经从数组列表中删除了“@mock”并对其进行了初始化,现在测试通过了。感谢您说明模拟列表会有多复杂。
【解决方案2】:

正如@Adam 所说:“使用for-each 语法在底层调用Collection.iterator() 遍历列表。这将返回null,因为您没有设置mockito 来返回任何其他内容。” 所以你必须以这种方式设置mockito;

    @Test
public void test_mergeShipments_increasesByOneWhenAShipmentOfOneAddedToAShipmentORderSizeOfTwo(){

      //GIVEN

   //Mock the iterator
    Iterator<Order> stockIteratorMock = mock(Iterator.class);

    //WHEN

    //In setUp method you put two objs
    when(mockShipmentOrder.size()).thenReturn(2); 

   //Set a mock for iterator
    when(mockShipmentOrder.iterator()).thenReturn(iteratorMock);

   // Instruct the iteratorMock when stop to return item
    when(iteratorMock.hasNext())
            .thenReturn(true)
            .thenReturn(true)
            .thenReturn(false);

    // Instruct the iteratorMock what obj return on each call
    // You can skip this: mockShipmentOrders.add(mockOrder1);
    when(stockIteratorMock.next())
      .thenReturn(mockOrder1)
      .thenReturn(mockOrder2);

    shipment.mergeShipments(shipment);

    //THEN
    assertEquals(2, shipment.getShipmentOrders().size());
}

这种方式比较冗长,但是你可以随意修改数组列表的行为,也可以理解它在木头下是如何工作的。

【讨论】:

    【解决方案3】:

    您不能向 Mocking 元素添加值。您可以从数据列表中删除 @Mock 并使用 new 关键字对其进行初始化。

    private Shipment shipment;
    private Shipment shipment2;
    @Mock
    private Order mockOrder1;
    @Mock
    private Order mockOrder2;
    @Mock
    private Order mockOrder3;
    
    private ArrayList<Order> mockShipmentOrders;
    
    private ArrayList<Order> mockShipmentOrders2;
    
    @Before
    public void setUp(){
        MockitoAnnotations.initMocks(this);
        mockShipmentOrders = new ArrayList<>();
        mockShipmentOrders2 = new ArrayList<>();
        mockShipmentOrders.add(mockOrder1);
        mockShipmentOrders.add(mockOrder2);
        mockShipmentOrders2.add(mockOrder3);
        shipment = new Shipment(1, mockShipmentOrders);
        shipment2 = new Shipment(2, mockShipmentOrders2);
    }
    
    @Test
    public void test_mergeShipments_increasesByOneWhenAShipmentOfOneAddedToAShipmentORderSizeOfTwo(){
        System.out.println(shipment);
        System.out.println(shipment2);
        shipment.mergeShipments(shipment2);
    
        assertEquals(3, shipment.getShipmentOrders().size());
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-11-29
      • 1970-01-01
      • 2014-03-21
      • 2021-04-11
      • 1970-01-01
      • 2019-06-06
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多