2012-01-17 53 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); 
     ... 
    } 
    ... 
相關問題