使用Mockito来mock autowired字段
依赖注入(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注解的被测试对象也可以显式实例化。