模拟一个列表并尝试迭代它

时间:2017-03-28 10:29:51

标签: java list loops testing mocking

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

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());
}
上面你可以看到我的模拟测试,下面是我的类,方法是:

公共舱发货{

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?

3 个答案:

答案 0 :(得分:2)

有一些基本问题导致您的示例无法正常工作并抛出NullPointerException

  1. 对模拟列表中的add()的调用实际上没有做任何事情。模拟中的所有无效方法都是&#34; no-ops&#34;默认情况下
  2. 使用for-each语法在列表中迭代Collection.iterator()。这将返回null,因为您没有设置mockito来返回任何其他内容。
  3. 相反,我不会模拟列表而是传递实际列表。 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);
        }
    }
    

答案 1 :(得分:0)

您无法为Mocked元素添加值。你可以从数据列表中删除@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());
}

答案 2 :(得分:0)

@Adam所说:“在后台使用for-each语法调用Collection.iterator()遍历一个列表。由于您没有设置mockito来返回任何其他内容,因此返回null。” 因此,您必须以这种方式设置模仿;

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

这种方式很冗长,但是您可以随意修改数组列表的行为,也可以了解其在后台的工作方式。