简体   繁体   English

如何在 @ComponentScan @Configuration 中使用数据源依赖项对 Spring 引导应用程序的 controller 进行单元测试

[英]How do I unit-test the controller of a Spring Boot application with a DataSource dependency in a @ComponentScan @Configuration

Consider the following basic Spring Boot application:考虑以下基本的 Spring Boot 应用程序:

@SpringBootApplication
@ComponentScan(basePackages = "webmvctestproblem.foo")
public class Application {
  public static void main(String[] args) {
    run(Application.class, args);
  }
}

It contains only two other beans.它只包含另外两个 bean。 One controller:一个 controller:

@RestController
class Greeter {
  @GetMapping("/")
  String greet() {
    return "Hello, world!";
  }
}

And one configuration in webmvctestproblem.foo containing a DataSource dependency: webmvctestproblem.foo中的一个配置包含DataSource依赖项:

@Configuration
class Bar {
  @Autowired
  private DataSource dataSource;
}

Running the application normally (through gradlew bootrun , eg) succeeds.正常运行应用程序(例如通过gradlew bootrun )成功。 Thus, confirming that the app is configured correctly under normal conditions.因此,确认应用程序在正常情况下配置正确。

However, running the following test causes a runtime error because Spring still attempts to resolve the data source bean dependency on the configuration class:但是,运行以下测试会导致运行时错误,因为 Spring 仍会尝试解析数据源 bean 对配置 class 的依赖性:

@RunWith(SpringRunner.class)
@WebMvcTest
public class GreeterTest {
  @Test
  public void test() throws Exception {
  }
}

Of course, there isn't one to resolve because the test is a @WebMvcTest that is designed to create only MVC-related beans.当然,没有一个可以解决,因为测试是一个@WebMvcTest ,它旨在仅创建与 MVC 相关的 bean。

How do I get rid of the error?我如何摆脱错误? I have already tried excluding the configuration class using the excludeFilters attribute of the existing @WebMvcTest annotation and a new @ComponentScan annotation on the test itself.我已经尝试使用现有@WebMvcTest注释的excludeFilters属性和测试本身的新@ComponentScan注释排除配置 class。 I don't want to resort to turning it into an integration test with @SpringBootTest .我不想求助于将它变成带有@SpringBootTest的集成测试。

(The project is also available on GitHub for convenience.) (为方便起见,该项目也可通过GitHub获得。)

If the DataSource is not mandatory for the test run, simply mock the DataSource with @MockBean in the test class.如果 DataSource 不是测试运行所必需的,只需在测试中使用@MockBean模拟 DataSource class。

@RunWith(SpringRunner.class)
@WebMvcTest
public class GreeterTest {
    @Autowired
    private MockMvc mockMvc;
    @MockBean
    private DataSource dataSource;

    @Test
    public void shouldGreet() throws Exception {
        mockMvc
                .perform(get("/"))
                .andExpect(content().string("Hello, world!"));
    }
}

Spring will automatically create a Mock for DataSource and inject it into the running test application context. Spring 会自动为DataSource 创建一个Mock 并将其注入到正在运行的测试应用上下文中。


Based on your source code it works.根据您的源代码,它可以工作。

(Btw: Your source code has a minor issue. The Greeter controller class is in the base package but the component scan only scans on the "foo" package. So there will be no Greeter controller on the test run if this isn't fixed.) (顺便说一句:您的源代码有一个小问题。Greeter controller class 在基础 package 中,但组件扫描仅扫描“foo”package。因此,如果这不是固定的,则测试运行中不会有 Greeter controller .)

@WebMvcTest creates a "slice" of all the beans relevant to WebMvc Testing (Controllers, Json conversion related stuff and so forth). @WebMvcTest创建与 WebMvc 测试相关的所有 bean 的“切片”(控制器、Json 转换相关的东西等等)。

You can examine the defaults in org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTypeExcludeFilter您可以检查org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTypeExcludeFilter中的默认值

In order to find which beans are actually supposed to be Run Spring must resolve them somehow, right?为了找到哪些 bean 实际上应该运行 Spring 必须以某种方式解决它们,对吗?

So spring test tries to understand what should be loaded and what not by passing through these filters.因此 spring 测试试图通过这些过滤器来了解应该加载什么,不应该加载什么。

Now, if you mark anything with @Configuration spring "knows" that this is the place where the place should be found.现在,如果你用@Configuration spring 标记任何东西,“知道”这是应该找到的地方。 So it will load the configuration and then will check which beans defined in this configuration must actually be loaded.因此它将加载配置,然后检查必须实际加载此配置中定义的哪些 bean。 However the object of configuration itself must be loaded anyway.但是无论如何必须加载配置本身的 object。

Now the process of loading the configuration object includes injecting stuff into these configurations - this is lifecycle of object creation of spring. And this is a source of mistake here:现在加载配置 object 的过程包括将内容注入到这些配置中——这是 object 创建 spring 的生命周期。这是这里的错误来源:

@Configuration
class Bar {
  @Autowired
  private DataSource dataSource;
}

Spring loads Bar and tries as a part of loading this object to autowire the data source. Spring 加载Bar并尝试作为加载此 object 的一部分来自动装配数据源。 This fails since the DataSource Bean itself is excluded by filters.这失败了,因为 DataSource Bean 本身被过滤器排除了。

Now in terms of solution:现在就解决方案而言:

First of all, why do you need this DataSource to be autowired in the Configuration object?首先,为什么需要在Configuration object 中自动装配此数据源? Probably you have the bean that uses it, lets call it "MyDao", otherwise I don't see a point of such a construction, since @Configuration -s are basically a place to define bean and you shouldn't put business logic there (if you do - ask a separate question and me/our colleagues will try to help and suggest better implementation).可能你有使用它的 bean,我们称它为“MyDao”,否则我看不到这种构造的意义,因为@Configuration -s 基本上是定义 bean 的地方,你不应该把业务逻辑放在那里(如果你这样做 - 问一个单独的问题,我/我们的同事会尽力帮助并建议更好的实施)。

So I assume you have something like this:所以我假设你有这样的事情:

public class MyDao {
   private final DataSource dataSource;

   public MyDao(DataSource dataSource) {
      this.dataSource = dataSource;
   }
}

@Configuration
class Bar {
  @Autowired
  private DataSource dataSource;

  @Bean
  public MyDao myDao() {
     return new MyDao(dataSource);
  }
}

In this case however you can rewrite the configuration in a different way:然而,在这种情况下,您可以用不同的方式重写配置:

@Configuration
class Bar {

  // Note, that now there is no autowired datasource and I inject the parameter in the bean instead - so that the DataSource will be required only if Spring will have to create that MyDao bean (which it won't obviously)
  @Bean
  public MyDao myDao(DataSource dataSource) {
     return new MyDao(dataSource);
  }
}

Now the Bar object will still be created - as I've explained above, but it beans including MyDao of course won't be created, problem solved!现在仍然会创建Bar object - 正如我在上面解释的那样,但它当然不会创建包含MyDao的 bean,问题已解决!

The solution with @Autowired(required=false) provided by @Anish B. should also work - spring will attempt to autowire but won't fail because the data source is unavailable, however you should think whether its an appropriate way to deal with this issue, your decision... @Anish B. 提供的@Autowired(required=false)解决方案也应该有效——spring 将尝试自动装配但不会因为数据源不可用而失败,但是你应该考虑它是否是处理此问题的合适方法问题,你的决定...

Consider the following basic Spring Boot application:考虑以下基本 Spring 引导应用程序:

@SpringBootApplication
@ComponentScan(basePackages = "webmvctestproblem.foo")
public class Application {
  public static void main(String[] args) {
    run(Application.class, args);
  }
}

It contains only two other beans.它只包含另外两个豆子。 One controller:一个 controller:

@RestController
class Greeter {
  @GetMapping("/")
  String greet() {
    return "Hello, world!";
  }
}

And one configuration in webmvctestproblem.foo containing a DataSource dependency: webmvctestproblem.foo中的一项配置包含DataSource依赖项:

@Configuration
class Bar {
  @Autowired
  private DataSource dataSource;
}

Running the application normally (through gradlew bootrun , eg) succeeds.正常运行应用程序(例如通过gradlew bootrun )成功。 Thus, confirming that the app is configured correctly under normal conditions.因此,确认应用程序在正常情况下配置正确。

However, running the following test causes a runtime error because Spring still attempts to resolve the data source bean dependency on the configuration class:但是,运行以下测试会导致运行时错误,因为 Spring 仍会尝试解决对配置 class 的数据源 bean 依赖性:

@RunWith(SpringRunner.class)
@WebMvcTest
public class GreeterTest {
  @Test
  public void test() throws Exception {
  }
}

Of course, there isn't one to resolve because the test is a @WebMvcTest that is designed to create only MVC-related beans.当然,没有要解决的问题,因为测试是一个@WebMvcTest ,旨在仅创建与 MVC 相关的 bean。

How do I get rid of the error?如何摆脱错误? I have already tried excluding the configuration class using the excludeFilters attribute of the existing @WebMvcTest annotation and a new @ComponentScan annotation on the test itself.我已经尝试使用现有@WebMvcTest注释的excludeFilters属性和测试本身的新@ComponentScan注释排除配置 class 。 I don't want to resort to turning it into an integration test with @SpringBootTest .我不想诉诸将它变成与@SpringBootTest的集成测试。

(The project is also available on GitHub for convenience.) (为方便起见, GitHub上也提供该项目。)

Before you can @Autowire the DataSource bean you need to define the DataSource in some config class or in the properties file.在您可以@Autowire 数据源 bean 之前,您需要在某些配置 class 或属性文件中定义数据源。 Something like this像这样的东西

spring.datasource.url = jdbc:mysql://localhost/abc
spring.datasource.name=testme
spring.datasource.username=xxxx
spring.datasource.password=xxxx
spring.datasource.driver-class-name= com.mysql.jdbc.Driver 
spring.jpa.database=mysql
spring.jpa.database-platform=org.hibernate.dialect.MySQLDialect

Or要么

@Configuration
public class JpaConfig {   
@Bean
public DataSource getDataSource() 
{
    DataSourceBuilder dataSourceBuilder = DataSourceBuilder.create();
    dataSourceBuilder.driverClassName("org.h2.Driver");
    dataSourceBuilder.url("jdbc:h2:file:C:/temp/test");
    dataSourceBuilder.username("sa");
    dataSourceBuilder.password("");
    return dataSourceBuilder.build();
}

You should use你应该使用

@AutoConfigureMockMvc
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@RunWith(SpringRunner.class)

on your test class, then you can inject在你的测试 class 上,然后你可以注入

   @Autowired
   private MockMvc mockMvc;

and use it in test并在测试中使用它

 mockMvc.perform(get("/"))
                .andExpect(status().isOk())
                .andReturn();

i suggest you read the documentation about testing.我建议您阅读有关测试的文档。 You can test a spring boot application in 100s of different ways.您可以用数百种不同的方式测试 spring 启动应用程序。

WebMvcTest WebMvc测试

as suggested by the documentation try, defining what controller class that you want to test in the @WebMvcTest annotation.按照文档的建议尝试,定义要在 @WebMvcTest 注释中测试的@WebMvcTest

@RunWith(SpringRunner.class)
@WebMvcTest(Greeter.class)
public class GreeterTest {

    @Autowired
    private MockMvc mockMvc;

    @Test
    public void shouldGreet() throws Exception {
        mockMvc
            .perform(get("/"))
            .andExpect(content().string("Hello, world!"));
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM