spring MVC controller 的單元測試 第一部分:配置

出處:http://www.petrikainulainen.net/programming/spring-framework/unit-testing-of-spring-mvc-controllers-configuration/

感謝作者!

spring MVCcontroller 的單元測試

 

第一部分:配置

Spring MVC Test ,使使我們可以通過DispatcherServlet來調用controller,進行測試

第一部分描述了controller的單元測試和配置方式

首先是需要的依賴

<dependency>

    <groupId>junit</groupId>

    <artifactId>junit</artifactId>

    <version>4.11</version>

    <scope>test</scope>

</dependency>

<dependency>

    <groupId>org.mockito</groupId>

    <artifactId>mockito-core</artifactId>

    <version>1.9.5</version>

    <scope>test</scope>

</dependency>

<dependency>

    <groupId>org.springframework</groupId>

    <artifactId>spring-test</artifactId>

    <version>3.2.3.RELEASE</version>

    <scope>test</scope>

</dependency>

 

 

示例使用的controller

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.context.MessageSource;

import org.springframework.stereotype.Controller;

 

@Controller

public class TodoController {

 

    private final TodoService service;

 

    private final MessageSource messageSource;

 

    @Autowired

    public TodoController(MessageSource messageSource, TodoService service) {

        this.messageSource = messageSource;

        this.service = service;

    }

 

    //Other methods are omitted.

}

 

配置application context

測試配置和產品配置分離是一件很麻煩的事,而且有時,我們會忘記將測試的修改同步到產品的配置中

 

因此,我們將context的配置分離,這樣,我們就可以在測試中重用部分配置,而不用單獨寫測試使用的配置

 

我們的配置分離如下

l   第一個配置是 “主”相關配置

l   第二個配置是  “web層”相關配置

l   第三個配置是  “持久化”相關配置

 

配置web層

l   開啓註解功能

l   指定靜態資源位置

l   確保靜態資源能夠被容器默認servlet使用

l   是controller能夠被掃描到

l   配置ExceptionResolver bean.

l   配置ViewResolver bean

web層 xml配置

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"

       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

       xmlns:mvc="http://www.springframework.org/schema/mvc"

       xmlns:context="http://www.springframework.org/schema/context"

       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd

       http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd

       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd">

 

    <mvc:annotation-driven/>

 

    <mvc:resources mapping="/static/**" location="/static/"/>

    <mvc:default-servlet-handler/>

 

    <context:component-scan base-package="net.petrikainulainen.spring.testmvc.common.controller"/>

    <context:component-scan base-package="net.petrikainulainen.spring.testmvc.todo.controller"/>

 

    <bean id="exceptionResolver" class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">

        <property name="exceptionMappings">

            <props>

                <prop key="net.petrikainulainen.spring.testmvc.todo.exception.TodoNotFoundException">error/404</prop>

                <prop key="java.lang.Exception">error/error</prop>

                <prop key="java.lang.RuntimeException">error/error</prop>

            </props>

        </property>

        <property name="statusCodes">

            <props>

                <prop key="error/404">404</prop>

                <prop key="error/error">500</prop>

            </props>

        </property>

    </bean>

 

    <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">

        <property name="prefix" value="/WEB-INF/jsp/"/>

        <property name="suffix" value=".jsp"/>

        <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>

    </bean>

</beans>

 

測試用context,主要配置controller的依賴

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"

       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

 

    <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">

        <property name="basename" value="i18n/messages"/>

        <property name="useCodeAsDefaultMessage" value="true"/>

    </bean>

 

    <bean id="todoService" name="todoService" class="org.mockito.Mockito" factory-method="mock">

        <constructor-arg value="net.petrikainulainen.spring.testmvc.todo.service.TodoService"/>

    </bean>

</beans>

 

 

配置測試類

我們有兩種方式配置測試類

l   使用單獨的配置。這種方式適用於應用很小。

l   基於產品使用的applicationContext 的配置。

 

單獨配置步驟:

l   Annotate the test class with the @RunWithannotation and ensure that the test is executed by using theSpringJUnit4ClassRunner.

l   Annotate the class with the@ContextConfiguration annotation and ensure that the correct configurationclasses (or XML configuration files) are used. If we want to use Javaconfiguration, we have to set the configuration classes as the value of theclasses attribute. On the other hand, if we prefer XML configuration, we haveto set the configuration files as the value of the locations attribute.

l   Annotate the class with the@WebAppConfiguration annotation. This annotation ensures that the applicationcontext which is loaded for our test is a WebApplicationContext.

l   Add a MockMvc field to the test class.

l   Add a TodoService field to the test classand annotate the field with the @Autowired annotation.

l   Add a WebApplicationContext field to thetest class and annotate the field with the @Autowired annotation.

l   Add a setUp() method to the test class andannotate the method with the @Before annotation. This ensures that the methodis called before each test. This method has responsibilities: it resets theservice mock before each test and create a new MockMvc object by calling thewebAppContextSetup() method of the MockMvcBuilders class.

 

單獨配置太麻煩、也不實用,就不詳細介紹了

 

import org.junit.Before;

import org.junit.runner.RunWith;

import org.mockito.Mock;

import org.mockito.runners.MockitoJUnitRunner;

import org.springframework.context.MessageSource;

import org.springframework.context.support.ResourceBundleMessageSource;

import org.springframework.test.web.servlet.MockMvc;

import org.springframework.test.web.servlet.setup.MockMvcBuilders;

import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;

import org.springframework.web.servlet.HandlerExceptionResolver;

import org.springframework.web.servlet.ViewResolver;

import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver;

import org.springframework.web.servlet.view.InternalResourceViewResolver;

import org.springframework.web.servlet.view.JstlView;

 

import java.util.Properties;

 

@RunWith(MockitoJUnitRunner.class)

public class StandaloneTodoControllerTest {

 

    private MockMvc mockMvc;

 

    @Mock

    private TodoService todoServiceMock;

 

    @Before

    public void setUp() {

        mockMvc = MockMvcBuilders.standaloneSetup(new TodoController(messageSource(), todoServiceMock))

                .setHandlerExceptionResolvers(exceptionResolver())

                .setValidator(validator())

                .setViewResolvers(viewResolver())

                .build();

    }

 

    private HandlerExceptionResolver exceptionResolver() {

        SimpleMappingExceptionResolver exceptionResolver = new SimpleMappingExceptionResolver();

 

        Properties exceptionMappings = new Properties();

 

        exceptionMappings.put("net.petrikainulainen.spring.testmvc.todo.exception.TodoNotFoundException", "error/404");

        exceptionMappings.put("java.lang.Exception", "error/error");

        exceptionMappings.put("java.lang.RuntimeException", "error/error");

 

        exceptionResolver.setExceptionMappings(exceptionMappings);

 

        Properties statusCodes = new Properties();

 

        statusCodes.put("error/404", "404");

        statusCodes.put("error/error", "500");

 

        exceptionResolver.setStatusCodes(statusCodes);

 

        return exceptionResolver;

    }

 

    private MessageSource messageSource() {

        ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();

 

        messageSource.setBasename("i18n/messages");

        messageSource.setUseCodeAsDefaultMessage(true);

 

        return messageSource;

    }

 

    private LocalValidatorFactoryBean validator() {

        return new LocalValidatorFactoryBean();

    }

 

    private ViewResolver viewResolver() {

        InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();

 

        viewResolver.setViewClass(JstlView.class);

        viewResolver.setPrefix("/WEB-INF/jsp/");

        viewResolver.setSuffix(".jsp");

 

        return viewResolver;

    }

}

 

使用基於配置文件的配置

步驟如下:

l   給類添加註解 @RunWith(SpringJUnit4ClassRunner)

l   給類添加註解 @ContextConfiguration 加載配置文件或配置類

l   給類添加註解 @WebAppConfiguration。這個註解確保應用上下文作爲webapplicationContext被加載

l   添加一個MockMvc屬性

l   作爲屬性,添加需要注入的依賴,並賦給他@AutoWired註解

l   添加一個WebApplicationContext屬性,並賦給他@AutoWired註解

l   添加setUp()方法,並賦給他@Before 註解。這個方法負責:reset service mock、創建一個新的MockMvc對象(通過調用MockMvcBuilders的webAppContextSetup() 方法)

importorg.junit.Before;

importorg.junit.runner.RunWith;

importorg.mockito.Mockito;

importorg.springframework.beans.factory.annotation.Autowired;

importorg.springframework.test.context.ContextConfiguration;

importorg.springframework.test.context.junit4.SpringJUnit4ClassRunner;

importorg.springframework.test.context.web.WebAppConfiguration;

importorg.springframework.test.web.servlet.MockMvc;

importorg.springframework.test.web.servlet.setup.MockMvcBuilders;

importorg.springframework.web.context.WebApplicationContext;

 

@RunWith(SpringJUnit4ClassRunner.class)

@ContextConfiguration(classes = {TestContext.class, WebAppContext.class})

//@ContextConfiguration(locations = {"classpath:testContext.xml", "classpath:exampleApplicationContext-web.xml"})

@WebAppConfiguration

publicclassWebApplicationContextTodoControllerTest {

 

    privateMockMvc mockMvc;

 

    @Autowired

    privateTodoService todoServiceMock;

 

    @Autowired

    privateWebApplicationContext webApplicationContext;

 

    @Before

    publicvoidsetUp() {

        //We have to reset our mock between tests because the mock objects

        //are managed by the Spring container. If we would not reset them,

        //stubbing and verified behavior would "leak" from one test to another.

        //這裏我也不是很明白什麼意思,大概意思就是必須調用Mockito.reset(todoServiceMock);

        //因爲mock是由容器管理的,而bean默認是單利,如果不重置的話,b

//ean的狀態可能不是初始化狀態

//(如果不對的話,歡迎指正)

        Mockito.reset(todoServiceMock);

        mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();

    }

}

 

 

最佳實踐:將配置分開是非常重要的,因爲我們可以重用配置。

 

這裏我要再次感謝原文作者的不辭辛苦

http://www.petrikainulainen.net/programming/spring-framework/unit-testing-of-spring-mvc-controllers-configuration/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章