[英]How to test Spring boot application configuration class with dependency injection of map?
[英]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 啟動應用程序。
按照文檔的建議嘗試,定義要在 @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.