1. 程式人生 > >Spring+SpirngMVC+Shiro+Junit4單元測試Controller方法

Spring+SpirngMVC+Shiro+Junit4單元測試Controller方法

版本:Spring 4.1.8 Spring MVC 4.1.8,Shiro 1.2.4,Junit 4.12

網上關於對Controller的測試其實挺多的,不過也挺雜亂的,遇到各種坑。

  • 首先用到的是MockMvc這個測試框架,這個沒什麼好說的;
  • 其次測試需要使用者登入的session問題,因為在controller中需要驗證使用者的資訊
  • controller方法中各個引數屬性註解的處理,如@ModelAttribute,@RequestBody

先上一個我自己專案的單元測試框架

單元測試基類

/**
 * spring 測試基類 使用junit4進行單元測試
 * 
 * @author
junehappylove * */
@RunWith(SpringJUnit4ClassRunner.class) @WebAppConfiguration // 載入配置檔案,可以指定多個配置檔案,locations指定的是一個數組 @ContextConfiguration(locations = { "classpath:app.xml", "classpath:shiro-config-nosession.xml", "classpath:quartz-task.xml", "classpath:spring-mvc.xml" }) // 啟動事務控制 @Transactional // 配置事務管理器,同時指定自動回滾
@TransactionConfiguration(transactionManager = "transactionManager", defaultRollback = true) public class BaseJunit4Test extends BaseController { }

定義一個單元測試的基類,以後其他所有測試類都要繼承BaseJunit4Test

業務測試

一個簡單測試例項如下:

public class WarningControllerTest extends BaseJunit4Test {
    @Autowired
    private
WarningController warningController; private MockMvc mockMvc; @Before public void setUp() throws Exception { mockMvc = MockMvcBuilders.standaloneSetup(warningController).build(); } @After public void tearDown() throws Exception { } // 。。。。後面是具體的測試方法定義,這裡省略 }

上面對應解決一般的測試問題,就已經足夠了
但是涉及到驗證使用者資訊的情況下,上面程式碼顯然不足,執行測試會發現SessionContext must be an HTTP compatible implementation.說白了就是沒有session資訊,那麼我沒就要新增Mock的session資訊

解決使用者session問題

首先需要引入屬性MockHttpSession屬性,其次還必須有使用者登入的Controller

public class WarningControllerTest extends BaseJunit4Test {
    @Autowired
    private WarningController warningController;
    @Autowired
    private UserLogin userLogin;
    private MockMvc mockMvc;
    private MockMvc mockMvc2;
    private MockHttpSession session;
    @Before
    public void setUp() throws Exception {
        mockMvc = MockMvcBuilders.standaloneSetup(warningController).build();
        mockMvc2 = MockMvcBuilders.standaloneSetup(userLogin).build();
        this.session = doLogin();
    }
    @After
    public void tearDown() throws Exception {
    }
    // 。。。。後面是具體的測試方法定義,這裡省略

    /**
     * 獲取使用者登入的Session
     * @return MockHttpSession
     * @throws Exception 異常
     */
    private MockHttpSession doLogin() throws Exception {
        ResultActions resultActions = this.mockMvc2.perform(MockMvcRequestBuilders.post("/userLogin/dologinDomain")
                .param("username", "username").param("password", "password").param("vcode", ""));
        resultActions.andExpect(MockMvcResultMatchers.status().isOk());
        MvcResult result = resultActions.andReturn();
        session = (MockHttpSession) result.getRequest().getSession();
        return session;
    }
}

上面程式碼中說明在做使用者驗證測試的時候,必須首先模擬一遍使用者登入,獲取到session再做後面的測試,doLogin方法只是一個測試,裡面提交的具體引數需要根據實際情況post。然後繼續執行後續的測試程式碼,還是會發現SessionContext must be an HTTP compatible implementation.異常,這個異常可以跟蹤原始碼查詢,不過這裡早就有大神做了這一步了,具體可以參考Junit+Spring MockMvc+Shiro時出現SessionContext和SecurityManager的錯誤解決方式
這位老兄說的很明白了,就是需要手動新增shiroFilter過濾器,那麼這裡就把

mockMvc = MockMvcBuilders.standaloneSetup(warningController).build();
mockMvc2 = MockMvcBuilders.standaloneSetup(userLogin).build();

修改如下:

mockMvc = MockMvcBuilders.standaloneSetup(warningController).addFilters((Filter) SpringUtils.getBean("shiroFilter")).build();
mockMvc2 = MockMvcBuilders.standaloneSetup(userLogin).addFilters((Filter) SpringUtils.getBean("shiroFilter")).build();

然後再測試就會發下正常了!

不過到了具體Controller方法又有問題,如下對@ModelAttribute @RequestBody的處理

Junit測試Controller(MockMVC使用),傳輸@ModelAttribute引數解決辦法

測試方法

@Test
public void testSearch() throws Exception {
    WarningModel model = new WarningModel();
    ResultActions resultActions = this.mockMvc
            .perform(MockMvcRequestBuilders.post("/warning/search").contentType(MediaType.APPLICATION_JSON)
                    .param("start", "1").param("limit", "2").session(session).flashAttr("model", model));
    resultActions.andExpect(MockMvcResultMatchers.status().isOk());
    resultActions.andDo(MockMvcResultHandlers.print()).andReturn();
}

上面方法測試一個查詢操作,其中有引數 start,limit,這兩個引數使用者控制分頁,還有引數model,這個物件裡面承載的是查詢的具體條件,使用flashAttr方法,然後定義了一個“model”的名稱,那麼在業務Controller的查詢條件中必須顯式的指名@ModelAttribute的名稱是”model”,否則單元測試不通過!

@RequestMapping("/search")
@ResponseBody
public ModelMap search(
        @RequestParam(value = Constants.DEFAULT_CURRPAGE_MODEL_KEY, required = false, defaultValue = Constants.DEFAULT_PAGE_START) Integer start,
        @RequestParam(value = Constants.DEFAULT_PAGE_SIZE_MODEL_KEY, required = false, defaultValue = Constants.DEFAULT_PAGE_SIZE) Integer limit,
        @ModelAttribute(value = "model") WarningModel model) {//必須指定value屬性名“model”
    ModelMap modelMap = new ModelMap();
    if (user() != null) {//驗證登入使用者合法性
        PageHelper.startPage(start, limit);
        List<WarningModel> list = warningService.query(model);
        modelMap.addAttribute(Constants.DEFAULT_RECORD_MODEL_KEY, list);
        modelMap.addAttribute(Constants.DEFAULT_COUNT_MODEL_KEY, ((Page<WarningModel>) list).getTotal());
        modelMap.addAttribute(Constants.DEFAULT_SUCCESS_KEY, Boolean.TRUE);
    } else {
        modelMap.addAttribute(Constants.DEFAULT_SUCCESS_KEY, Boolean.FALSE);
    }
    return modelMap;
}

Junit測試Controller(MockMVC使用),傳輸@RequestBody資料解決辦法

關於引數是@RequestBody修飾,表明前臺傳來的是json串,測試模擬提交型別必須是application/json,並且使用content方法提交json內容,如下:

@Test
@Rollback
public void testDeleteExist() throws Exception {
    String content = "[{\"appid\":1}]";
    this.mockMvc
            .perform(MockMvcRequestBuilders.post("/warning/delete").session(session)
                    .contentType(MediaType.APPLICATION_JSON).content(content))
            .andExpect(MockMvcResultMatchers.status().isOk()).andDo(MockMvcResultHandlers.print()).andReturn();
}

好了,以上就是總結的使用Spring+SpirngMVC+Shiro+Junit4單元測試Controller方法以及注意事項