使用Mockito进行单元测试

5

我正在为我的Spring应用程序编写服务层的单元测试。
这是我的服务类:

    @Service
    public class StubRequestService implements RequestService {    
        @Autowired
        private RequestDao requestDao;  

        @Transactional(propagation = Propagation.REQUIRED, readOnly = true)
        @Override
        public Request getRequest(Long RequestId) {
            Request dataRequest = requestDao.find(requestId);
            return dataRequest;
        }
    }  

这是我的测试类

@RunWith(MockitoJUnitRunner.class)
@ContextConfiguration(locations = { "/META-INF/spring/applicationContext.xml" })
public class StubRequestServiceTest {

    @Mock
    public RequestDao requestDao;

    StubRequestService stubRequestService;  // How can we Autowire this ?

    @org.junit.Before
    public void init() {
      stubRequestService = new StubRequestService();  // to avoid this 
      stubRequestService.setRequestDao(dataRequestDao);  
      // Is it necessary to explicitly set all autowired elements ?  
      // If I comment/remove above setter then I get nullPointerException 
    }

    @Test
    public void testGetRequest()  {
        Request request = new Request();
        request.setPatientCnt("3");
        when(requestDao.find(anyLong())).thenReturn(request);
        assertEquals(stubRequestService.getRequest(1234L).getPatientCnt(),3);
    }    
}   

它工作得很好,但我有几个问题

  1. 如何在测试中Autowire服务类?我在init()方法中使用构造函数来创建服务对象。
  2. 我们是否必须为服务类设置所有Autowire元素?例如,StubRequestService已经自动装配了RequestDao,在调用测试方法之前,我需要显式设置它,否则会在StubRequestService.getRequest方法中出现nullPointerException,因为requestDao在该方法中为空。
  3. 在单元测试Spring服务层时应遵循哪些良好的实践?(如果我做错了什么)

如果你在回答给出后修改了问题,那么这些答案就不再有意义了。我会撤销你最近的编辑。 - JB Nizet
@JB:对修改问题表示歉意。我只是想提供正确和准确的信息。谢谢。 - Ajinkya
3个回答

7

你的测试很好,甚至不需要有@ContextConfiguration注解。

像Spring这样的依赖注入框架的整个重点是能够通过简单地实例化它们、设置模拟依赖项,然后调用它们的方法来对服务进行单元测试。

你做得很正确。对于这种单元测试,你不需要一个Spring上下文。这就是为什么它们被称为单元测试:它们在所有实际依赖项的隔离环境中进行测试,包括Spring。

另外:假设你正在使用JUnit,assertXxx方法的参数应该交换。期望值先于实际值。当断言失败时,“期望6但实际是3”比“期望3但实际是6”更重要。


感谢您的回答和建议。这是意味着我们应该显式地创建服务对象并设置所有自动装配的依赖关系吗?我被建议使用自动装配而不是手动设置。 - Ajinkya
1
自动装配在运行应用程序时是可以的。但在单元测试时,它不是必需的,甚至不被期望,因为每个测试都希望注入自己的模拟依赖项。 - JB Nizet

3

3
  1. If you really feel that it will make your tests easier to understand - you can initialize a spring context and fetch all of the objects from there. However, usually it will require creating a separate spring configuration XML file specifically for tests therefore I would not recommend it.

    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("testApplicationContext.xml");
    stubRequestService = (RequestService)applicationContext.getBean("myRequestServiceBean");
    
  2. (and 3) Basically, I prefer testing each component of my application in total isolation from eachother and that's why I do not recommend what I described in [1].

这意味着您需要对应用程序进行逻辑上的分割,并仅测试该部分,同时完全模拟其尝试访问的所有内容。

假设您有三个类:

//Fetches stuff from some webservice and converts to your app domain POJOs
class DataAccessLayer {
    public void setWebservice(Webservice ws) {...};

    public MyObject getMyObject() {...};
}

//Formats the domain POJOs and sends them to some kind of outputstream or stuff.
class ViewLayer {
    public void setOutputStream(OutputStream os) {...};

    public void viewMyObject(MyObject mo) {...};
}

//Main entry point of our MyObject fetch-process-display workflow
class Controller {
    public void setDataAccessLayer(DataAccessLayer dal) {...};
    public void setViewLayer(ViewLayer vl) {...};

    public void showMyObject() {
        MyObject mo = dal.getMyObject();
        ...some processing here maybe...
        vl.viewMyObject(mo);
    }
}

现在,在这里我们可以写哪些测试?

  1. 测试DataAccessLayer是否正确将对象从模拟的WS转换为我们的领域对象。
  2. 测试ViewLayer是否正确格式化给定的对象并将其写入模拟的输出流。
  3. 测试Controller是否从模拟的DataAccessLayer获取对象并正确处理该对象,然后将其发送到模拟的ViewLayer

在测试中实例化bean时,使用不同的上下文文件是否有特定的原因?感谢提供示例,它确实帮了很大的忙。 - Ajinkya
没有理由,通常只是因为它与你的测试不兼容。例如它需要一些 JNDI 资源,可能加载了一些数据库(而测试从未使用它们),也可能存在某些安全性问题。所以最终你会注意到,为测试创建一个单独的上下文文件更容易。 - bezmax
是的,有一个原因:你不想用真正的DAO来测试服务。你需要一个模拟的DAO来测试服务。但是请遵循Max和我的建议:不要使用Spring上下文来单元测试服务。你可能想在DAO测试中使用Spring上下文来注入数据源、SessionFactory和TxManager,但不要在服务测试中使用。 - JB Nizet
@JBNizet 嗯,有时在测试中使用Spring上下文是值得的。例如,当您正在对某个依赖于特定配置的大型组件进行集成测试时。 - bezmax
@Max:是的,这就是我在之前评论中所建议的。例如,在测试依赖于数据库、TxManager等DAO时非常有用。对于纯业务服务,这不应该是必需的。 - JB Nizet
除非测试基础设施代码,否则您不应该在测试中自己管理应用程序上下文。相反,使用 Spring 测试框架 - Ryan Stewart

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接