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

考慮以下基本的 Spring Boot 應用程序:

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

它只包含另外兩個 bean。 一個 controller:

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

webmvctestproblem.foo中的一個配置包含DataSource依賴項:

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

正常運行應用程序(例如通過gradlew bootrun )成功。 因此,確認應用程序在正常情況下配置正確。

但是,運行以下測試會導致運行時錯誤,因為 Spring 仍會嘗試解析數據源 bean 對配置 class 的依賴性:

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

當然,沒有一個可以解決,因為測試是一個@WebMvcTest ,它旨在僅創建與 MVC 相關的 bean。

我如何擺脫錯誤? 我已經嘗試使用現有@WebMvcTest注釋的excludeFilters屬性和測試本身的新@ComponentScan注釋排除配置 class。 我不想求助於將它變成帶有@SpringBootTest的集成測試。

(為方便起見,該項目也可通過GitHub獲得。)

如果 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 會自動為DataSource 創建一個Mock 並將其注入到正在運行的測試應用上下文中。


根據您的源代碼,它可以工作。

(順便說一句:您的源代碼有一個小問題。Greeter controller class 在基礎 package 中,但組件掃描僅掃描“foo”package。因此,如果這不是固定的,則測試運行中不會有 Greeter controller .)

@WebMvcTest創建與 WebMvc 測試相關的所有 bean 的“切片”(控制器、Json 轉換相關的東西等等)。

您可以檢查org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTypeExcludeFilter中的默認值

為了找到哪些 bean 實際上應該運行 Spring 必須以某種方式解決它們,對嗎?

因此 spring 測試試圖通過這些過濾器來了解應該加載什么,不應該加載什么。

現在,如果你用@Configuration spring 標記任何東西,“知道”這是應該找到的地方。 因此它將加載配置,然后檢查必須實際加載此配置中定義的哪些 bean。 但是無論如何必須加載配置本身的 object。

現在加載配置 object 的過程包括將內容注入到這些配置中——這是 object 創建 spring 的生命周期。這是這里的錯誤來源:

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

Spring 加載Bar並嘗試作為加載此 object 的一部分來自動裝配數據源。 這失敗了,因為 DataSource Bean 本身被過濾器排除了。

現在就解決方案而言:

首先,為什么需要在Configuration object 中自動裝配此數據源? 可能你有使用它的 bean,我們稱它為“MyDao”,否則我看不到這種構造的意義,因為@Configuration -s 基本上是定義 bean 的地方,你不應該把業務邏輯放在那里(如果你這樣做 - 問一個單獨的問題,我/我們的同事會盡力幫助並建議更好的實施)。

所以我假設你有這樣的事情:

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);
  }
}

然而,在這種情況下,您可以用不同的方式重寫配置:

@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);
  }
}

現在仍然會創建Bar object - 正如我在上面解釋的那樣,但它當然不會創建包含MyDao的 bean,問題已解決!

@Anish B. 提供的@Autowired(required=false)解決方案也應該有效——spring 將嘗試自動裝配但不會因為數據源不可用而失敗,但是你應該考慮它是否是處理此問題的合適方法問題,你的決定...

考慮以下基本 Spring 引導應用程序:

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

它只包含另外兩個豆子。 一個 controller:

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

webmvctestproblem.foo中的一項配置包含DataSource依賴項:

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

正常運行應用程序(例如通過gradlew bootrun )成功。 因此,確認應用程序在正常情況下配置正確。

但是,運行以下測試會導致運行時錯誤,因為 Spring 仍會嘗試解決對配置 class 的數據源 bean 依賴性:

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

當然,沒有要解決的問題,因為測試是一個@WebMvcTest ,旨在僅創建與 MVC 相關的 bean。

如何擺脫錯誤? 我已經嘗試使用現有@WebMvcTest注釋的excludeFilters屬性和測試本身的新@ComponentScan注釋排除配置 class 。 我不想訴諸將它變成與@SpringBootTest的集成測試。

(為方便起見, GitHub上也提供該項目。)

在您可以@Autowire 數據源 bean 之前,您需要在某些配置 class 或屬性文件中定義數據源。 像這樣的東西

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

要么

@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();
}

你應該使用

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

在你的測試 class 上,然后你可以注入

   @Autowired
   private MockMvc mockMvc;

並在測試中使用它

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

我建議您閱讀有關測試的文檔。 您可以用數百種不同的方式測試 spring 啟動應用程序。

WebMvc測試

按照文檔的建議嘗試,定義要在 @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