【Java.JUnit】Unit Testing of Spring MVC Controllers: Configuration

本文介绍如何为 Spring MVC 控制器编写单元测试,并详细解释了如何配置测试环境,包括使用 Maven 获取依赖、配置程序上下文及测试类等。

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

Unit Testing of Spring MVC Controllers: Configuration

 

Writing unit tests for Spring MVC controllers has traditionally been both simple and problematic.

Although it is pretty simple to write unit tests which invoke controller methods, the problem is that those unit tests are not comprehensive enough.

For example, we cannot test controller mappings, validation and exception handling just by invoking the tested controller method.

Spring MVC Test solved this problem by giving us the possibility to invoke controller methods through the DispatcherServlet.

This is the first part of my tutorial which describes the unit testing of Spring MVC controllers and it describes how we can configure our unit tests.

Let’s get started.

为Spring MVC controllers写单元测试一直都是既简单又有问题.

虽然写调用controller方法的单元测试非常简单, 但问题在于那些测试并不够全面

例如, 仅仅调用测试的controller方法,我们不能测试controller的映射,验证和异常处理

Spring MVC Test通过给予我们通过DispatcherServlet调用controller方法的能力解决了这个问题

这是我Spring MVC controllers单元测试文章系列的第一部分, 内容是我们如何配置我们的单元测试

让我开始吧.

Getting the Required Dependencies with Maven

使用Maven获取需要的依赖包

We can get the required dependencies by declaring the following testing dependencies in ourpom.xml file:

我们可以在pom.xml文件中声明来获取以下需要的包

  • JUnit 4.11
  • Mockito Core 1.9.5
  • Spring Test 3.2.3.RELEASE

The relevant part of our pom.xml file looks as follows:

我们的pom.xml的相关部分如下:

<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>

Let’s move on and take a quick look at our example application.

让我们继续, 快速看看我们的例子程序

The Anatomy of Our Example Application

实例程序剖析

The example application of this tutorial provides CRUD operations for todo entries. In order to understand the configuration of our test class, we must have some knowledge about the tested controller class.

这个教程的示例程序为todo条目提供了CRUD操作.为了理解我们的测试类的配置, 我们必须有一些关于测试controller的类的只是

At this point, we need to know the answers to these questions:

此时此刻,我们需要知道下列问题的答案:

  • What dependencies does it have?
  • 他有那些依赖
  • How is it instantiated?
  • 他是如何实例化的

We can get the answers to those questions by taking a look at the source code of theTodoController class. The relevant part of the TodoController class looks as follows:

看一看TodoController类的源代码我们可以找到那些问题的答案.相关的TodoController类的代码如下:

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.
}

As we can see, our controller class has two dependencies: TodoService and MessageSource. Also, we can see that our controller class uses constructor injection.

如我们所见, 我们的controller类有两个依赖: TodoService和MessageSource. 我们也可以看到controller使用了构造方法注入.

At this point this is all there information we need. Next we will talk about our application context configuration.

这就是此时我们需要的所有信息. 下面我们会谈谈我们程序上下文的设置

Configuring the Application Context

配置程序上下文

Maintaining a separate application context configurations for our application and our tests is cumbersome. Also, It can lead into problems if we change something in the application context configuration of our application but forget to do the same change for our test context.

为我们的程序和测试维护一个分离的程序上下文配置是很麻烦的. 如果我们改了我们程序的上下文设置但是忘了修改我们测试的上下文会导致一些问题.

That is why the application context configuration of the example application has been divided in a such way that we can reuse parts of it in our tests.

这就是为什么实例程序的上文环境被这样切分的原因, 我们可以在测试中重用它

Our application context configuration has been divided as follows:

我们程序的上下文配置被分成如下

  • The first application configuration class is called ExampleApplicationContext and it is the “main” configuration class of our application.
  • 第一个程序配置类叫做ExampleApplicationContext, 他是我们程序的"main"配置
  • The second configuration class is responsible of configuring the web layer of our application. The name of this class is WebAppContext and it is the configuration class which we will use in our tests.
  • 第二个配置类负责配置程序的web层.他的名字叫WebAppContext,我们会在测试中用到它
  • The third configuration class is called PersistenceContext and it contains the persistence configuration of our application.
  • 第三个配置类教唆PersistenceContext, 他包括程序的持久配置

Note: The example application has also a working application context configuration which uses XML configuration files. The XML configuration files which correspond with the Java configuration classes are: exampleApplicationContext.xml, exampleApplicationContext-web.xml andexampleApplicationContext-persistence.xml.

注意: 实例程序也有一个工作程序上下文配置, 它使用XML配置文件配置. 和这几个Java配置类一致的配置文件是: exampleApplicationContext.xml, exampleApplicationContext-web.xml, exampleApplicationContext-persistence.xml

Let’s take a look at the application context configuration of our web layer and find out how we can configure our test context.

让我们看一看我们的web层程序上下文配置和配置测试上下文的方法

Configuring the Web Layer

配置web层

The application context configuration of the web layer has the following responsibilities:

程序web层上下文配置有如下职责

  1. It enables the annotation driven Spring MVC.
  2. It configures the location of static resources such as CSS files and Javascript files.
  3. It ensures that the static resources are served by the container’s default servlet.
  4. It ensures that the controller classes are found during component scan.
  5. It configures the ExceptionResolver bean.
  6. It configures the ViewResolver bean.
  1. 开启Spring MVC注解驱动
  2. 设置静态资源如CSS和JS文件的路径
  3. 保证静态资源由容器的默认servlet提供
  4. 保证controller类在组件扫描时被找到
  5. 配置 ExceptionResolver bean
  6. 配置 ViewResolver bean

Let’s move on and take a look at the Java configuration class and the XML configuration file.

让我们继续, 看看Java的配置类和XML配置文件

Java Configuration

Java 配置

If we use Java configuration, the source code of the WebAppContext class looks as follows:

如果我们使用Java配置, WebAppContext类的源代码如下

复制代码
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
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;

@Configuration
@EnableWebMvc
@ComponentScan(basePackages = {
        "net.petrikainulainen.spring.testmvc.common.controller",
        "net.petrikainulainen.spring.testmvc.todo.controller"
})
public class WebAppContext extends WebMvcConfigurerAdapter {

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/static/**").addResourceLocations("/static/");
    }

    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }

    @Bean
    public SimpleMappingExceptionResolver 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;
    }

    @Bean
    public ViewResolver viewResolver() {
        InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();

        viewResolver.setViewClass(JstlView.class);
        viewResolver.setPrefix("/WEB-INF/jsp/");
        viewResolver.setSuffix(".jsp");

        return viewResolver;
    }
}
复制代码

 

XML Configuration

XML 配置文件

If we use XML configuration, the content of the exampleApplicationContext-web.xml file looks as follows:

如果我们使用XML配置, exampleApplicationContext-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>
复制代码

 

Configuring the Test Context

配置测试上下文

The configuration of our test context has two responsibilities:

测试上下文配置的职责有2个:

  1. It configures a MessageSource bean which is used by our controller class (feedback messages) and Spring MVC (validation error messages). The reason why we need to do this is that theMessageSource bean is configured in the “main” configuration class (or file) of our application context configuration.
  2. It creates a TodoService mock which is injected to our controller class.
  1. 配置一个给controller类使用(反馈信息)和Spring MVC(验证错误信息)使用的MessageSource bean. 我们需要这么做的原因是 MessageSource bean是在我们程序上下文配置的"main"配置类(或文件)配置的 
  2. 他创建一个TodoService mock, 用来注入到controller类中

Let’s find out how we configure our test context by using Java configuration class and XML configuration file.

让我们看看我们如何用Java配置类和配置文件来配置我们的测试上下文

Java Configuration

If we configure our test context by using Java configuration, the source code of the TestContextclass looks as follows:

如果我们使用Java配置来配置测试环境, TestContext的源代码如下:

复制代码
import org.mockito.Mockito;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ResourceBundleMessageSource;

@Configuration
public class TestContext {

    @Bean
    public MessageSource messageSource() {
        ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();

        messageSource.setBasename("i18n/messages");
        messageSource.setUseCodeAsDefaultMessage(true);

        return messageSource;
    }

    @Bean
    public TodoService todoService() {
        return Mockito.mock(TodoService.class);
    }
}
复制代码

 

XML Configuration

If we configure our test context by using an XML configuration, the content of thetestContext.xml file looks as follow:

如果使用XML文件来配置测试上下文, testContext.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"
      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>
复制代码

 

Configuring The Test Class

配置测试类

We can configure our test class by using one of the following options:

我们可以通过如下选择二选一来配置我们的测试类:

  1. The Standalone configuration allows us to register one or more controllers (classes annotated with the @Controller annotation) and configure the Spring MVC infrastructure programatically. This approach is a viable option if our Spring MVC configuration is simple and straight-forward.
  2. The WebApplicationContext based configuration allows us the configure Spring MVC infrastructure by using a fully initialized WebApplicationContext. This approach is better if our Spring MVC configuration is so complicated that using standalone configuration does not make any sense.
  1. 单独的配置允许我们注册一个或多个controller(通过在类上使用@Controller注释标记)并编程式配置Spring MVC的基础设施.如果Spring MVC配置足够简单和直接, 这个方法是可行的
  2. 基于配置的WebApplicationContext允许我们通过完全初始化一个WebApplicationContext来配置Spring MVC的基础设施. 如果我们的Spring MVC配置很复杂, 使用单独的配置不好使的话,这将会是很好的方法

Let’s move on and find out how we can configure our test class by using both configuration options.

让我们看看使用这两种方法如何配置test类

Using Standalone Configuration

使用单独的配置

We can configure our test class by following these steps:

我们可以按如下步骤配置我们的测试类:

  1. Annotate the class with the @RunWith annotation and ensure that test is executed by using the MockitoJUnitRunner.
  2. Add a MockMvc field to the test class.
  3. Add a TodoService field to the test class and annotate the field with the @Mock annotation. This annotation marks the field as a mock. The field is initialized by the MockitoJUnitRunner.
  4. Add a private exceptionResolver() method to the class. This method creates a newSimpleMappingExceptionResolver object, configures it, and returns the created object.
  5. Add a private messageSource() method to the class. This method creates a newResourceBundleMessageSource object, configures it, and returns the created object.
  6. Add a private validator() method to the class. This method creates a newLocalValidatorFactoryBean object and returns the created object.
  7. Add a private viewResolver() method to the the class. This method creates a newInternalResourceViewResolver object, configures it, and returns the created object.
  8. Add a setUp() method to the test class and annotate the method with the @Beforeannotation. This ensures that the method is invoked before each test. This method creates a new MockMvc object by calling the standaloneSetup() method of the MockMvcBuilders class and configures the Spring MVC infrastructure programmatically.
  1. 使用@RunWith注释类,保证测试是使用 MockitoJUnitRunner 执行的
  2. 加一个 MockMvc 字段到测试类中
  3. 加一个TodoService字段到test类中,并使用@Mock注释标记他. 这个注释让这个字段编程mock对象. 这个字段使用MockitoJUnitRunner初始化
  4. 增加一个private exceptionResolver()方法到类中.这个方法创建一个新的SimpleMappingExceptionResolver对象,配置他并返回创建的对象
  5. 增加一个private messageSource()方法到类中.这个方法创建一个新的ResourceBundleMessageSource对象,配置他并返回创建的对象
  6. 增加一个private validator()方法到类中.这个方法创建一个新的LocalValidatorFactoryBean对象并返回创建的对象
  7. 增加一个private viewResolver()方法到类中.这个方法创建一个新的InternalResourceViewResolver对象,配置他并返回创建的对象.
  8. 增加一个setUp()方法到测试类中并用@Before注释标记.这保证了方法在每次测试之前执行.这个方法通过调用MockMvcBuilders类的standaloneSetup()创建一个新的MockMvc对象,并通过编码配置了Spring MVC的基础设置

The source code of our test class looks as follows:

源代码如下

复制代码
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;
    }
}
复制代码

 

Using the standalone configuration has two problems:

使用单独的配置有两个问题

  1. Our test class looks like a mess even though our Spring MVC configuration is rather simple. Naturally, we could clean it up by moving the creation of Spring MVC infrastructure components into a separate class. This is left as an exercise for the reader.
  2. We have to duplicate the configuration of Spring MVC infrastructure components. This means that if we change something in the application context configuration of our application, we must remember to do the same change to our tests as well.
  1. 就算Spring MVC的配置很简单,测试类看起来也是一团糟. 当然,我们可以将这些创建Spring MVC基础设施的代码移到一个单独的class中. 这个就留作为读者的练习好了.
  2. 我们不得不重复写一次Spring MVC基础设置组件的配置.这意味着我们改变了程序的上下文的时候, 必须对我们的测试上下文做出相同的修改.

Using WebApplicationContext Based Configuration

使用 基于WebApplicationContext的配置

We can configure our test class by following these steps:

我们可以按如下步骤配置我们的测试类:

  1. Annotate the test class with the @RunWith annotation and ensure that the test is executed by using the SpringJUnit4ClassRunner.
  2. Annotate the class with the @ContextConfiguration annotation and ensure that the correct configuration classes (or XML configuration files) are used. If we want to use Java configuration, we have to set the configuration classes as the value of the classes attribute. On the other hand, if we prefer XML configuration, we have to set the configuration files as the value of the locations attribute.
  3. Annotate the class with the @WebAppConfiguration annotation. This annotation ensures that the application context which is loaded for our test is a WebApplicationContext.
  4. Add a MockMvc field to the test class.
  5. Add a TodoService field to the test class and annotate the field with the @Autowiredannotation.
  6. Add a WebApplicationContext field to the test class and annotate the field with the@Autowired annotation.
  7. Add a setUp() method to the test class and annotate the method with the @Before annotation. This ensures that the method is called before each test. This method has responsibilities: it resets the service mock before each test and create a new MockMvc object by calling the webAppContextSetup() method of the MockMvcBuilders class.
  1. 使用@RunWith注释标记测试类, 并保证测试是使用SpringJUnit4ClassRunner执行的
  2. 使用@ContextConfiguration注释并保证使用正确的配置类(或者XML配置文件). 如果我们先使用Java配置,就必须设置配置类为 classes 参数的值. 而如果线使用XML配置, 则必须设置配置文件为locations参数的值
  3. 使用@WebAppConfiguration注释标记类.这个注释保证了我们的测试加载的程序上下文是一个WebApplicationContext, 他的作用是表明我们的测试程序会使用默认的web根目录 -- "src/main/webapp", 另外, 他必须和ContextConfiguration结合使用.如果要修改web根目录的值, 使用value参数覆盖它
  4. 加入一个 MockMvc字段到测试类中.
  5. 加入一个TodoService字段到测试类中,并使用@Autowired注释标注他
  6. 加入一个WebApplicationContext字段到测试类中,并使用@Autowired注释标注他
  7. 加入一个setUp()方法到测试类中,并使用@Before注释标注他.这保证了这个方法会在每次测试之前被调用.这个方法有如下职责: 他在每次测试前重置service mock并通过调用MockMvcBuilders类的webAppContextSetup创建新的MockMvc对象.

The source code of our test class looks as follows:

源代码如下

复制代码
import org.junit.Before;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {TestContext.class, WebAppContext.class})
//@ContextConfiguration(locations = {"classpath:testContext.xml", "classpath:exampleApplicationContext-web.xml"})
@WebAppConfiguration
public class WebApplicationContextTodoControllerTest {

    private MockMvc mockMvc;

    @Autowired
    private TodoService todoServiceMock;

    @Autowired
    private WebApplicationContext webApplicationContext;

    @Before
    public void setUp() {
        //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);

        mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
    }
}
复制代码

 

The configuration of our test class looks a lot cleaner than the configuration which uses standalone configuration. However, the “downside” is that our test uses the full Spring MVC infrastructure. This might be an overkill if our test class really uses only a few components.

这种测试类的配置方法看起来比使用单独的配置类的方法清晰多了.然而, 坏处就是我们的测试使用了完全的Spring MVC基础设置.如果我们仅仅使用一小部分组件这可能会太过于消耗性能

Summary
总结

We have now configured our unit test class by using both the standalone setup and theWebApplicationContext based setup. This blog post has taught us two things:

我们现在已经通过单独配置和基于WebApplicationContext的方法配置好了单元测试类. 这篇blog教会了我们两件事情

  • We learned that it is important to divide the application context configuration in a such way that we can reuse parts of it in our tests.
  • 我们学到了分离程序上下文以在测试中重用他们的重要性
  • We learned the difference between the standalone configuration and theWebApplicationContext based configuration.
  • 我们学到了单独配置和基于WebApplicationContext的配置的重要性

The next part of this tutorial describes how we can write unit tests for “normal” Spring MVC controllers.

下一部分教程将会描述如何为普通的SpringMVC contoller写单元测试

P.S. The example application of this blog post is available at Github.

另外, 实例程序已经发布到了Github上


考虑柔性负荷的综合能源系统低碳经济优化调度【考虑碳交易机制】(Matlab代码实现)内容概要:本文围绕“考虑柔性负荷的综合能源系统低碳经济优化调度”展开,重点研究在碳交易机制下如何实现综合能源系统的低碳化与经济性协同优化。通过构建包含风电、光伏、储能、柔性负荷等多种能源形式的系统模型,结合碳交易成本与能源调度成本,提出优化调度策略,以降低碳排放并提升系统运行经济性。文中采用Matlab进行仿真代码实现,验证了所提模型在平衡能源供需、平抑可再生能源波动、引导柔性负荷参与调度等方面的有效性,为低碳能源系统的设计与运行提供了技术支撑。; 适合人群:具备一定电力系统、能源系统背景,熟悉Matlab编程,从事能源优化、低碳调度、综合能源系统等相关领域研究的研究生、科研人员及工程技术人员。; 使用场景及目标:①研究碳交易机制对综合能源系统调度决策的影响;②实现柔性负荷在削峰填谷、促进可再生能源消纳中的作用;③掌握基于Matlab的能源系统建模与优化求解方法;④为实际综合能源项目提供低碳经济调度方案参考。; 阅读建议:建议读者结合Matlab代码深入理解模型构建与求解过程,重点关注目标函数设计、约束条件设置及碳交易成本的量化方式,可进一步扩展至多能互补、需求响应等场景进行二次开发与仿真验证。
org.junit.runners.model.InvalidTestClassError: Invalid test class 'com.ruoyi.web.test.UserInfoServiceTest': 1. No runnable methods at org.junit.runners.ParentRunner.validate(ParentRunner.java:525) at org.junit.runners.ParentRunner.<init>(ParentRunner.java:92) at org.junit.runners.BlockJUnit4ClassRunner.<init>(BlockJUnit4ClassRunner.java:74) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.<init>(SpringJUnit4ClassRunner.java:137) at org.springframework.test.context.junit4.SpringRunner.<init>(SpringRunner.java:49) at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:77) at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:499) at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:480) at org.junit.internal.builders.AnnotatedBuilder.buildRunner(AnnotatedBuilder.java:104) at org.junit.internal.builders.AnnotatedBuilder.runnerForClass(AnnotatedBuilder.java:86) at org.junit.runners.model.RunnerBuilder.safeRunnerForClass(RunnerBuilder.java:70) at org.junit.internal.builders.AllDefaultPossibilitiesBuilder.runnerForClass(AllDefaultPossibilitiesBuilder.java:37) at org.junit.runners.model.RunnerBuilder.safeRunnerForClass(RunnerBuilder.java:70) at org.junit.internal.requests.ClassRequest.createRunner(ClassRequest.java:28) at org.junit.internal.requests.MemoizingRequest.getRunner(MemoizingRequest.java:19) at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:50) at com.intellij.rt.junit.IdeaTestRunner$Repeater$1.execute(IdeaTestRunner.java:38) at com.intellij.rt.execution.junit.TestsRepeater.repeat(TestsRepeater.java:11) at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:35) at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:235) at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:54)
03-29
<think>好的,我现在需要帮助用户解决他们遇到的JUnit错误:`org.junit.runners.model.InvalidTestClassError: Invalid test class 'com.ruoyi.web.test.UserInfoServiceTest': 1. No runnable methods`。首先,我得理解这个错误的含义和可能的原因。 用户提供的错误信息指出测试类无效,原因是“No runnable methods”。这意味着JUnit在指定的测试类中找不到任何可以执行的方法。接下来,我需要回想JUnit的基本要求和常见的配置问题,可能导致这个错误的情况有哪些。 首先,JUnit的测试方法需要满足几个条件。例如,它们应该使用`@Test`注解,并且方法应该是public、void,并且没有参数。如果用户遗漏了这些中的任何一个,比如忘记添加@Test,或者方法不是public,就会导致JUnit无法识别该方法作为测试用例。 其次,检查测试类本身是否有问题。例如,测试类是否被正确注解,比如使用了`@RunWith`或者Spring的测试注解,如`@SpringBootTest`。如果用户错误地配置了测试运行器,或者类上有其他不兼容的注解,也可能导致此类问题。 另外,可能的情况是测试类中的方法被错误地标记为非public,比如使用了private或protected修饰符。JUnit要求测试方法必须是public的,否则无法发现它们。还有可能方法是静态的,或者带有参数,这些都不符合JUnit的要求。 还有可能用户的测试类中确实没有任何测试方法,这可能是一个疏忽,比如忘记编写实际的测试方法,或者测试方法被错误地命名,没有遵循约定的命名模式。虽然JUnit主要依赖注解而不是方法名,但某些旧版本或特定配置可能会有不同的要求。 此外,依赖问题也不能排除。如果项目中JUnit的依赖不正确,或者版本冲突,可能导致注解无法被正确处理。例如,使用JUnit 5的`@Test`注解(来自org.junit.jupiter.api)但使用了JUnit 4的测试运行器,就会导致测试方法不被识别。 接下来,我需要考虑用户可能使用的是什么框架。错误堆栈中提到了`SpringRunner`,说明用户可能在使用Spring的测试框架,结合JUnit4。这时候,测试类可能需要同时使用`@RunWith(SpringRunner.class)`和`@SpringBootTest`等注解。如果这些注解缺失或配置错误,也可能导致问题。 现在,我需要将这些可能性整理成步骤,帮助用户逐一排查。首先检查测试方法是否有`@Test`注解,确保使用的是正确的包(JUnit4的`org.junit.Test`)。然后确认测试方法是public、void、无参数。接着检查测试类是否有必要的注解,如`@RunWith`。然后查看依赖是否正确,特别是JUnitSpring Test的版本是否兼容。最后,确认测试类中确实存在测试方法,没有被注释掉或拼写错误。 另外,用户可能在IDE中遇到了缓存问题,有时候重新构建项目或清理缓存可以解决此类问题。或者,测试类的编译结果没有正确生成,可能需要重新编译。 最后,用户可能需要查看具体的测试类代码,根据上述可能的原因进行对比,找出具体的问题所在。如果用户能提供测试类的代码片段,可以更准确地诊断问题,但目前需要根据现有信息给出通用的解决步骤。</think>### 问题分析:`InvalidTestClassError: No runnable methods` 这个错误表明 JUnit 在测试类 `UserInfoServiceTest` 中找不到可执行的测试方法。常见原因如下: --- ### 一、直接原因排查 #### 1. 缺少 `@Test` 注解 - **必须条件**:所有测试方法必须标注 `@Test` 注解。 - **错误示例**: ```java public void testMethod() { ... } // 缺少 @Test ``` - **修复方法**: ```java import org.junit.Test; // 确保导包正确(JUnit4) @Test public void testMethod() { ... } ``` #### 2. 测试方法不符合规范 - **要求**:方法必须满足以下条件: - 修饰符为 `public` - 返回类型为 `void` - 无参数 - **错误示例**: ```java @Test private void testMethod() { ... } // 错误:private 方法 ``` ```java @Test public String testMethod() { ... } // 错误:非 void 返回类型 ``` #### 3. 测试类缺少 `@RunWith` 注解 - **Spring 测试场景**:若使用 Spring Boot 测试,需添加 `@RunWith` 注解。 - **修复方法**: ```java import org.junit.runner.RunWith; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; @RunWith(SpringRunner.class) // JUnit4 需要此注解 @SpringBootTest public class UserInfoServiceTest { ... } ``` --- ### 二、依赖问题排查 #### 1. JUnit 版本冲突 - **JUnit4 vs JUnit5**: - JUnit4 的 `@Test` 包路径:`org.junit.Test` - JUnit5 的 `@Test` 包路径:`org.junit.jupiter.api.Test` - **错误现象**:若错误导入 JUnit5 的注解但使用 JUnit4 运行器,会导致方法不可识别。 - **修复方法**:确保依赖统一: ```xml <!-- Maven 依赖示例(JUnit4) --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13.2</version> <scope>test</scope> </dependency> ``` #### 2. Spring Test 依赖缺失 - **Spring Boot 项目**:需包含 `spring-boot-starter-test`。 ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> ``` --- ### 三、其他可能性 #### 1. 方法被过滤 - **条件过滤**:检查是否通过 `@Ignore` 注解或 JUnit 过滤器跳过了测试方法。 - **错误示例**: ```java @Test @Ignore // 方法被忽略 public void testMethod() { ... } ``` #### 2. IDE 或构建工具问题 - **缓存问题**:尝试清理并重新构建项目。 - **操作步骤**: - **IntelliJ**:`File > Invalidate Caches / Restart` - **Maven**:执行 `mvn clean test-compile` --- ### 四、完整代码示例 ```java import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; @RunWith(SpringRunner.class) @SpringBootTest public class UserInfoServiceTest { @Test public void testExample() { // public + void + 无参数 // 测试逻辑 } } ``` --- ### 总结步骤 1. 检查测试方法是否标注 `@Test`(包路径正确)。 2. 确保方法是 `public void` 且无参数。 3. 添加 `@RunWith(SpringRunner.class)` 注解(Spring 项目)。 4. 验证依赖无冲突(JUnit4 或 JUnit5 一致性)。 5. 清理项目缓存并重新编译。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值