Issue
Using Spring Boot 2.7.3
I can not create a simple integration test for my API using @WebMvcTest
.
Here is my setup:
// GameServerApplicationTests.kt
@SpringBootTest
class GameServerApplicationTests {
@Test
fun contextLoads() { }
}
// CraftService.kt
@Service
class CraftService {
fun getAll(): List<String> {
return listOf("foo", "bar")
}
}
// CraftApiTest.kt
@WebMvcTest
@Import(value = [CraftService::class])
class CraftApiTest {
@Autowired
private lateinit var testRestTemplate: TestRestTemplate
@Test
fun `should do accept craft all endpoint`() {
val response = testRestTemplate.getForEntity("/craft/all", String::class.java)
assertThat(response.statusCode).isEqualTo(HttpStatus.OK)
}
}
When I run the test I see this exception:
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'itemRepository' defined in com.gameserver.item.ItemRepository defined in @EnableJpaRepositories declared on GameServerApplication: Cannot create inner bean '(inner bean)#3fba233d' of type [org.springframework.orm.jpa.SharedEntityManagerCreator] while setting bean property 'entityManager'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name '(inner bean)#3fba233d': Cannot resolve reference to bean 'entityManagerFactory' while setting constructor argument; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'entityManagerFactory' available
I have no idea why it is looking for the itemRepository
bean at all. I never asked for that.
I then added this
@WebMvcTest
@ComponentScan(excludeFilters = [ComponentScan.Filter(Repository::class)]) // <<
@Import(value = [CraftService::class])
Which resulted in this exception:
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'playerRepository' defined in com.gameserver.player.PlayerRepository defined in @EnableJpaRepositories declared on GameServerApplication: Cannot create inner bean '(inner bean)#30c1da48' of type [org.springframework.orm.jpa.SharedEntityManagerCreator] while setting bean property 'entityManager'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name '(inner bean)#30c1da48': Cannot resolve reference to bean 'entityManagerFactory' while setting constructor argument; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'entityManagerFactory' available
Which confuses me even more. I explictly excluded all @Repository
beans - but it just skipped ItemRepository
and then asked for PlayerRepository
now.
I am totally lost and have no idea why I am not able to setup a simple integration test for my API endpoint.
EDIT #1:
Other tests run just fine:
EDIT #2:
I tried to use a @Configuration
bean for @Import
.
// CraftApiTestConfiguration
@Configuration
class CraftApiTestConfiguration {
@Bean
fun getCraftService(): CraftService {
return CraftService()
}
}
// CraftApiTest.kt
@WebMvcTest
@Import(CraftApiTestConfiguration::class)
class CraftApiTest { // ... }
That did not help either. It just gave me the second exception mentioned above (the one asking for playerRepository
)
Solution
I'll try to answer although without seeing the actual code it might not be correct.
So @WebMvcTest
loads a "slice" of your application with all the beans annotated with @RestControllers
. It doesn't load @Service
or @Repository
annotated beans.
When you run the test with @WebMvcTest
annotation it will load all the controllers, and if, by accident the controller references others than the reference on the service (here I can't say for sure what it is), you might end up loading the stuff that you don't actually need.
Now when you use @WebMvcTest
there are two things you can/should do:
Work with MockMvc instead of rest template that queries a web server, its not a full-fledged web layer test anyway.
Try using
@WebMvcTest
with your controller only:
@WebMvcTest(CraftApisController.class)
Also instead of injecting the real implementation of service, you can use @MockBean
so that the real service implementation will be covered by a regular unit test (without spring at all, just plain JUnit/Mockito) and this test could check that your annotations are defined correctly
Answered By - Mark Bramnik
Answer Checked By - Terry (JavaFixing Volunteer)