2012-01-17 72 views
11

我对Mockito和jUnit非常陌生,我尝试学习正确的TDD方法。我需要夫妇的例子,以便我可以使用mockito编写单元测试如何使用mockito编写控制器类的单元测试用例

以下是我的控制器类,它上传文件并对此文件输入执行一些操作。

@Controller 
@RequestMapping("/registration") 
public class RegistrationController { 

    @Autowired 
    private RegistrationService RegistrationService; 

    @Value("#{Properties['uploadfile.location']}") 
    private String uploadFileLocation; 

    public RegistrationController() { 

    } 

    @RequestMapping(method = RequestMethod.GET) 
    public String getUploadForm(Model model) { 
     model.addAttribute(new Registration()); 
     return "is/Registration"; 
    } 

    @RequestMapping(method = RequestMethod.POST) 
    public String create(Registration registration, BindingResult result,ModelMap model) 
      throws NumberFormatException, Exception { 

     File uploadedFile = uploadFile(registration); 
     List<Registration> userDetails = new ArrayList<Registration>(); 
     processUploadedFile(uploadedFile,userDetails); 

     model.addAttribute("userDetails", userDetails); 

     return "registration"; 
    } 

    private File uploadFile(Registration registration) { 

     Date dt = new Date(); 
     SimpleDateFormat format = new SimpleDateFormat("MM_dd_yyyy_HH_mm_ss"); 
     File uploadedFile = new File(uploadFileLocation 
       + registration.getFileData().getOriginalFilename() + "." 
       + format.format(dt)); 

      registration.getFileData().transferTo(uploadedFile); 

     return uploadedFile; 
    } 

    private void processUploadedFile(File uploadedFile, List<Registration> userDetails) 
      throws NumberFormatException, Exception { 

     registrationService.processFile(uploadedFile, userDetails); 
    } 

} 

任何机构可以请提出了一些例子,我怎么能写测试用例此使用的Mockito?

编辑 我写下下面的测试类,但如何进行进一步

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

    @InjectMocks 
    private RegistrationService registrationService= new RegistrationServiceImpl(); 
    @Mock 
    private final ModelMap model=new ModelMap(); 

    @InjectMocks 
    private ApplicationContext applicationContext; 

    private static MockHttpServletRequest request; 
    private static MockHttpServletResponse response; 

    private static RegistrationController registrationController; 

    @BeforeClass 
    public static void init() { 

      request = new MockHttpServletRequest(); 
      response = new MockHttpServletResponse();   
      registrationController = new RegistrationController(); 

    } 
    public void testCreate() 
    { 
     final String target = "bulkRegistration"; 
     BulkRegistration bulkRegistration=new BulkRegistration(); 
     final BindingResult result=new BindingResult();  

     String nextPage=null;  
     nextPage = bulkRegistrationController.create(bulkRegistration, result, model); 
     assertEquals("Controller is not requesting the correct form",nextPage, 
       target); 

    } 

} 
+0

我曾在这里问过类似的问题> http://stackoverflow.com/questions/9138555/spring-framework-test-restful-web-service-controller-offline-ie-no-server-n有是另外两个从帖子到帖子链接的问题。我正在使用** spring-test-mvc **框架来测试REST控制器。所以,希望我的问题中讨论的anwers/code能够帮助你。祝你好运! – jsf 2012-02-06 19:35:00

回答

13

有几件事情你似乎已经在你的测试中混淆了。有集成测试和单元测试。集成测试将测试所有的东西(或者几乎所有东西),所以你可以使用非常接近真实的配置文件,并将实际的对象实例注入到被测试的类中。这主要是我用@ContextConfiguration但我用的是与@RunWith(SpringJUnit4ClassRunner.class)来一起

如果您正在使用的Mockito(或任何模拟框架),这通常是因为要隔离您从测试的类其他类的真正实现。因此,例如,不必设法让RegistrationService引发NumberFormatException来测试代码路径,只需告诉模拟RegistrationService即可。还有很多其他示例使用mock比使用真实类实例更方便。

那么,那个迷你课程就完成了。以下是我将如何重新编写测试课程(附带一个额外的示例并沿途评论)。

@RunWith(MockitoJUnitRunner.class) 
public class RegistrationControllerTest { 

    // Create an instance of what you are going to test. 
    // When using the @InjectMocks annotation, you must create the instance in 
    // the constructor or in the field declaration. 
    @InjectMocks 
    private RegistrationController controllerUT = new RegistrationController(); 

    // The @Mock annotation creates the mock instance of the class and 
    // automatically injects into the object annotated with @InjectMocks (if 
    // possible). 
    @Mock 
    private RegistrationService registrationService; 
    // This @Mock annotation simply creates a mock instance. There is nowhere to 
    // inject it. Depending on the particular circumstance, it may be better or 
    // clearer to instantiate the mock explicitly in the test itself, but we're 
    // doing it here for illustration. Also, I don't know what your real class 
    // is like, but it may be more appropriate to just instantiate a real one 
    // than a mock one. 
    @Mock 
    private ModelMap model; 
    // Same as above 
    @Mock 
    private BulkRegistration bulkRegistration; 
    // Same as above 
    @Mock 
    private FileData fileData; 

    @Before 
    public void setUp() { 
     // We want to make sure that when we call getFileData(), it returns 
     // something non-null, so we return the mock of fileData. 
     when(bulkRegistration.getFileData()).thenReturn(fileData); 
    } 

    /** 
    * This test very narrowly tests the correct next page. That is why there is 
    * so little expectation setting on the mocks. If you want to test other 
    * things, such as behavior when you get an exception or having the expected 
    * filename, you would write other tests. 
    */ 
    @Test 
    public void testCreate() throws Exception { 
     final String target = "bulkRegistration"; 
     // Here we create a default instance of BindingResult. You don't need to 
     // mock everything. 
     BindingResult result = new BindingResult(); 

     String nextPage = null; 
     // Perform the action 
     nextPage = controllerUT.create(bulkRegistration, result, model); 
     // Assert the result. This test fails, but it's for the right reason - 
     // you expect "bulkRegistration", but you get "registration". 
     assertEquals("Controller is not requesting the correct form", nextPage, 
       target); 

    } 

    /** 
    * Here is a simple example to simulate an exception being thrown by one of 
    * the collaborators. 
    * 
    * @throws Exception 
    */ 
    @Test(expected = NumberFormatException.class) 
    public void testCreateWithNumberFormatException() throws Exception { 
     doThrow(new NumberFormatException()).when(registrationService) 
       .processFile(any(File.class), anyList()); 
     BindingResult result = new BindingResult(); 
     // Perform the action 
     controllerUT.create(bulkRegistration, result, model); 
    } 
} 
+0

为了记住这一点,请将规范的实现添加到POM中,例如:glassfish-embedded-all,否则将出现“缺席代码错误”。 – Sergio 2014-05-17 00:56:30

2

真正的问题是:如何设置这是使用Spring应用程序的测试环境?这个问题的答案并不简单,它取决于你的web应用程序的工作方式。

您应该首先关注如何对Java Web应用程序进行JUnit,然后如何使用Mockito。

1

Mockito是一个模拟对象的模拟框架。当您测试一个依赖于某个其他对象的方法结果的方法时,这通常是可行的。例如,在测试你的创建方法时,你会想模拟uploadedFile变量,因为在这里你不想测试uploadFile(Registration registration)是否工作正常(你在其他一些测试中测试它),但你有兴趣测试如果该方法正在处理上传的文件,并且它正在模型中添加details。嘲笑上传文件,你可以去:

但是,然后你看到这显示了一个设计问题。您的方法uploadFile()不应该驻留在控制器中,而应该放在其他实用程序类中。然后你可以@Mock该实用程序类而不是控制器。

你必须记住,如果你的代码很难测试,这表明你没有尽力保持简单。

1

看你的代码示例上面我看到几个问题:

  1. 使用的的Mockito的一点是要嘲笑你的类的依赖性。这将使您能够使用简单的JUnit测试用例。因此不需要使用@ContextConfiguration。您应该能够使用new运算符实例化正在测试的类,然后提供所需的依赖关系。

  2. 您正在使用自动装配来提供您的注册服务。为了注入此服务的模拟实例,您将需要使用Spring测试专用字段访问实用程序。

  3. 我无法从您的代码中看到RegistrationService是否是一个接口。如果不是,你会有嘲笑它的问题。

0

其他建议:不要使用Mockito。 Spring有自己的测试类,可以用来模拟,你可以使用SpringJUnit4ClassRunner。使用Spring JUnit测试运行器可以加载完整的Spring配置(通过@ContextConfiguration)以及模拟对象。在你的情况下,你的大部分实例化代码都会消失,因为你将运行Spring,而不是模仿它的DI。

1

我对Mockito不熟悉(因为我使用JMock),但使用mock编写测试的一般方法是相同的。

首先你需要一个被测试类(CUT)的实例(RegistrationController)。这绝不是模拟 - 因为你想测试它。

对于测试getUploadForm CUT实例不需要任何依赖关系,因此您可以通过new RegistrationController创建它。

那么你应该有一个测试的帽子看起来有点像这样

RegistrationController controller = new RegistrationController(); 
Model model = new Model(); 
String result = controller(model); 
assertEquals("is/Registration", result); 
assertSomeContstrainsFormodel 

这很简单。

要测试的下一个方法是create方法。这是非常困难的。

  • 你需要有参数对象(BindingResult)的实例可能是一个比较复杂一点
  • 您需要处理测试(后来删除)的文件 - 我将不讨论这个问题。但是,您是否应该考虑使用临时文件进行测试的方法。
  • 您使用这两个变量registrationServiceuploadFileLocation - 这是有趣的部分。

uploadFileLocation只是一个必须在测试中设置的字段。最简单的方法是添加一个(getter和)setter来设置测试中提交的文件。您也可以使用org.springframework.test.util.ReflectionTestUtils来设置此字段。 - 两种方式都有优点和缺点。

更有趣的是registrationService。这应该是一个模拟!您需要为该类创建一个模拟,然后在CUT实例中“注入”该模拟。就像uploadFileLocation一样,你至少有两个相同的选择。

然后,您需要定义您对模拟的异常情况:使用正确的文件和用户详细信息调用registrationService.processFile(uploadedFile, userDetails)。 (这个例外的确切定义是Mockito的一部分 - 我还没有足够的知识)。

然后你需要调用你想要在CUT上测试的方法。

顺便说一句:如果你需要经常在Spring beans上“注入”mock,那么你可以构建你自己的util。得到一个对象的实例,扫描该对象的字段与@Inject注释,创建模拟和“注入”嘲笑。 (然后你只需要getter来访问mock来定义那里的期望。) - 我已经为JMock构建了这样的工具,并且它对我有很大的帮助。

2

这是绝对有可能用的Mockito(或JMock的)嘲讽其依赖关系jherricks上面显示编写Spring MVC控制器纯单元测试。仍然存在的挑战是,使用带注释的POJO控制器有很多仍然未经测试 - 基本上所有内容都是在注释中表达并且在调用控制器时由框架完成。

支持测试Spring MVC控制器正在进行中(请参阅spring-test-mvc project)。虽然该项目仍将发生变化,但它现在可以使用。如果你对变化敏感,但不应该依赖它。无论哪种方式,我觉得值得指出的是,如果你想跟踪它或参与其发展。有一个每晚的快照,如果你想锁定一个特定的版本,本月将会有一个里程碑版本。

0

试试这个。

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

    @Mock 
    private RegistrationService registrationService; 

    //Controller that is being tested. 
    @Autowired 
    @InjectMocks 
    private RegistrationController registrationController; 

    @Before 
    public void setUp() { 
     MockitoAnnotations.initMocks(this); 
     ... 
    } 
    ... 
相关问题