依赖注入(DI)是像Spring,EJB这样的控制反转(IOC)类容器的一个很强大的特性。把注入的值封装成类的私有字段是一个很好的主意,但是像这样把autowired的字段封装起来也降低了可测试性。本文将介绍针对这种情况,Mockito是如何来mock私有的autowired字段的。

首先被测试的类依赖的第一个类是这样子的,它是一个Spring单例bean,这个类将会在测试中被mock掉。


@Repository
public class OrderDao {
  public Order getOrder(int irderId){
    throw new UnsupportedOperationException("Fail is not mocked!");
  }
}

接下来是被测试的类依赖的第二个类,它也是Spring的一个Component。在测试中,这个类将会被spied(partially mocked)。它的方法calculatePriceForOrder将会原样被调用(不被stub),另外一个方法将会被stubbed。

@Service
public class PriceService {

  public int getActualPrice(Item item){
    throw new UnsupportedOperationException("Fail is not mocked!");
  }

  public int calculatePriceForOrder(Order order){
    int orderPrice = 0;
    for (Item item : order.getItems()){
      orderPrice += getActualPrice(item);
    }
    return orderPrice;
  }

}

然后是被测试的类,它使用autowire上面的两个依赖的类。

@Service
public class OrderService {

  @Autowired
  private PriceService priceService;

  @Autowired
  private OrderDao orderDao;

  public int getOrderPrice(int orderId){
    Order order = orderDao.getOrder(orderId);
    return priceService.calculatePriceForOrder(order);
  }

}

最后是测试类,它使用字段级别的注解:

  • @InjectMocks – 含有此注解的字段,Mockito将会实例化被测试的类,并且尝试把用@Mock或者@Spy注解的字段注入到被测试对象的私有字段中。
  • @Mock – 含有此注解的字段,Mockito会创建对应类的mock实例。
  • @Spy – 含有此注解的字段,Mockito会创建对应类的spy实例。
public class OrderServiceTest {

  private static final int TEST_ORDER_ID = 15;
  private static final int TEST_SHOES_PRICE = 2;
  private static final int TEST_SHIRT_PRICE = 1;

  @InjectMocks
  private OrderService testingObject;
  @Spy
  private PriceService priceService;
  @Mock
  private OrderDao orderDao;

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

  @Test
  public void testGetOrderService(){
    Order order = new Order(Arrays.asList(Item.SHOES, Item.SHIRT));
    Mockito.when(orderDao.getOrder(TEST_ORDER_ID)).thenReturn(order);
    //notice different Mockito syntax for spy
    Mockito.doReturn(TEST_SHIRT_PRICE).when(priceService).getActualPrice(Item.SHIRT);
    Mockito.doReturn(TEST_SHOES_PRICE).when(priceService).getActualPrice(Item.SHOES);
    //call testing method
    int actualOrderPrice = testingObject.getOrderPrice(TEST_ORDER_ID);
    Assert.assertEquals(TEST_SHIRT_PRICE + TEST_SHOES_PRICE, actualOrderPrice);
  }

}

让我们看下在执行这个测试类的时候会发生什么: 1)首先JUnit框架会在执行每个测试方法之前执行@Before注解的方法initMocks。 2)initMocks这个方法会调用Mockito的方法(MockitoAnnotations.initMocks(this))初始化被被注解的字段。如果没有这个调用,那么被注解的字段将会是null。也可以使用@RunWith(MockitoJUnitRunner.class)来注解被测试的类来达成相同的效果。 3)最后当所有的字段被初始化完成后,开始执行测试方法(被@Test注解的方法)。

这个例子中不包含Spring context的创建,这里的Spring注解(比如@Service,@Repository)只是为了说明实际生产中代码的例子。测试本身不包含对Spring的任何依赖并且或忽略Spring的任何注解。事实上这里可以换成EJB的注解,或者甚至换成普通的私有字段(不被任何IoC容器管理),测试依然可以执行。

开发者可能认为每次测试之前的 MockitoAnnotations.initMocks(this)调用是不必要的开销。但是其实这样很方便,因为它重置了被测试的对象和重新初始化了mock对象。当你的测试类里有多个测试方法(@Test注解的方法)时,可以避免互相干扰。

@Spy注解的对象可以通过两种方法来初始化: 1)如果有默认的构造器,则可以由Mockito框架来自动创建; 2)如果仅有非默认构造器,可以显示实例化它。比如

@Spy
private PriceService priceService = new PriceService();

类似的,被@InjectMocks注解的被测试对象也可以显式实例化。

参考:https://lkrnac.net/blog/2014/01/mock-autowired-fields/