Spring Boot Controller Test: mocking service requiring downstream objects causing ApplicationContext to not load - spring

I am attempting to run a controller-level Spring Boot unit test with a Mock for my service-layer dependency. However, this Mock is requiring a downstream repository dependency which uses an EntityManager object, which is causing my test to fail on loading the ApplicationContext.
My test does not involve the repository dependency or EntityManager, it is using the Mocked service object to return a canned response. Why is Spring complaining about the repo/EntityManager if I only want to mock the service-layer object?
Controller unit test code:
#RunWith(SpringRunner.class)
#WebMvcTest
#AutoConfigureWebClient
public class MobileWearControllerTest {
#Autowired
private MockMvc mockMvc;
#MockBean
UserDeviceService userDeviceService;
//.....
}
UserDeviceService code:
#Service
public class UserDeviceService {
private UserDeviceRepository userDeviceRepository;
public UserDeviceService(UserDeviceRepository userDeviceRepository) {
this.userDeviceRepository = userDeviceRepository;
}
//....
}
UserDeviceRepository code:
#Repository
public class UserDeviceRepositoryImpl implements UserDeviceRepositoryCustom {
#PersistenceContext
private EntityManager em;
//....
}
Expecting the test to run.
Actual result is getting the following stack trace:
java.lang.IllegalStateException: Failed to load ApplicationContext
...
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'userDeviceRepositoryImpl': Injection of persistence dependencies failed; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'javax.persistence.EntityManagerFactory' available
...
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'javax.persistence.EntityManagerFactory' available
...

My issue was the annotations I was using for my test.
Using #AutoConfigureWebClient tries to standup the entire Spring Context; since I am unit testing my controller I want to test only the web layer and mock the downstream dependencies (ie UserDeviceService). So, I should be using #SpringBootTest and #AutoConfigureMockMvc instead, which will set up my Spring context only for the controller layer.
Using this approach I'm able to get UserDeviceService to mock successfully and thus allowing my test to compile and run:
#RunWith(SpringRunner.class)
#SpringBootTest
#AutoConfigureMockMvc
public class MobileWearControllerTest {
#Autowired
private MockMvc mockMvc;
#MockBean
UserDeviceService userDeviceService;
//...
}

First, you need to specify what controllers you are going to test
#WebMvcTest(YourController.class)
Additionally, With JUnit5, you don't need to configure any extensions, as #WebMvcTest contains #ExtendWith(SpringExtension.class). You are apparently on JUnit4, but this shouldn't do any harm.
Check for example https://spring.io/guides/gs/testing-web/

Related

Exclude elasticsearchTemplate from Spring-Boot Test

I have an application that use Elasticsearch and I'd like to disable this integration when I'm testing some controllers. How can I disable elasticsearchTemplate on Spring-Boot test?
Application.class:
#SpringBootApplication
#EnableElasticsearchRepositories(basePackages = "com.closeupinternational.comclosure.elasticsearch")
public class Application {
...
Repository.class:
#Repository
public interface PipelineRepository extends ElasticsearchRepository<Pipeline, String> {
...
Test Controller.class:
#ExtendWith(SpringExtension.class)
#EnableAutoConfiguration(exclude = {ElasticsearchDataAutoConfiguration.class,
ElasticsearchRepositoriesAutoConfiguration.class})
#WebMvcTest(ProductionCycleExecutionController.class)
#Slf4j
public class ProductionCycleExecutionControllerTest {
#Autowired
private MockMvc mvc;
#MockBean
private ProductionCycleExecutionService prodCycleExecService;
...
I'm not using inside ProductionCycleExecutionService and I don't wanna try to test elasticsearch repository PipelineRepository at this moment.
Error:
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'pipelineRepository' defined in
com.closeupinternational.comclosure.elasticsearch.PipelineRepository defined in
#EnableElasticsearchRepositories declared on Application: Cannot resolve reference to bean
'elasticsearchTemplate' while setting bean property 'elasticsearchOperations'; nested exception is org.springframework.beans.factory
Just remove #ExtendWith(SpringExtension.class) and #EnableAutoConfiguration(exclude = {ElasticsearchDataAutoConfiguration.class, ElasticsearchRepositoriesAutoConfiguration.class})
These annotations aim to bootstrap the whole Spring context and configure it
#WebMvcTest should be enough in your case as it bootstraps web-related context only
Upd
If you have any dependencies in your ProductionCycleExecutionController (like elasticsearchTemplate you mentioned) then mock them like this if you don't need to define their behavior, as follows:
#MockBeans(value = {#MockBean(YourBean1.class), #MockBean(YourBean2.class), #MockBean(YourBean3.class)})
If you do need to define mocking behavior then mock as property in class:
#MockBean
private YourBean yourBean;

#webMvcTest is not excluding and loading beans marked as #Repository

I've a #RestController which has only one dependency in field #Autowire
that dependency is #component, that component Class definition has some autowired fields which are #service and those services have some #repositories.
In this whole flow I've kafka, Quartz, Cassandra and DB2
So when I was creating a Unit test case for my controller, I dont want to setup whole application. so I decided to use #webMvcTest and used #MockBean on my only one dependency of controller class.
But my Test is throwing and exception because its trying to create a Dao bean, which is marked as #repository.
#ActiveProfiles("test")
#WebMvcTest(controllers = MyControllerTest .class)
class MyControllerTest {
#MockBean
MyControllerDependency dependency;
#Autowired
MockMvc mockMvc;
#Test
void test_something() throws Exception {
assert(true);
}
}
Here is oversimplified version of code
#Component
class MyControllerDependency {
#AutoiWired
MyCustomService service;
}
#Service
class MyCustomService{
#Autowired
MyCustomDao dao;
}
#Repository
class MyCustomDao{
#Autowired
private JdbcTemplate template;
}
I'm getting following exception in test.
Exception
***************************
APPLICATION FAILED TO START
***************************
Description:
Field template in com.....MyCustomDao` required a bean of type 'org.springframework.jdbc.core.JdbcTemplate' that could not be found.
Question is, When I'm using #WebMvcTest slice and already mocking the only required dependency MyControllerDependency then why spring test context is trying to load MyCustomDao which is annotated as #Repository.
I can do integration testing with SpringbootTest & AutoconfigureMockMVC, but for writing Junit test just for controller, I need to use WebMvcTest slice. which is creating a problem.
I ran into a similar problem where I want to test only my controller using #WebMvcTest, but the spring context was trying to create nondependent spring beans and was failing as below.
Failed to load ApplicationContext
java.lang.IllegalStateException: Failed to load ApplicationContext
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'TestController' defined in file ...
Solution: load only the controller your testing for an example like #ContextConfiguration(classes = DemoController.class).
Also, find below a complete sample
#WebMvcTest
#ContextConfiguration(classes = DemoController.class)
public class DemoControllerTest {
#Autowired
MockMvc mockMvc;
#MockBean
DemoService demoService;
#Test
public void testGetAllProductCodes_withOutData() throws Exception {
when(productCodeService.getAllProductCodes()).thenReturn(new ArrayList<ProductCodes>());
mockMvc.perform(MockMvcRequestBuilders.get("/services/productCodes")).andExpect(MockMvcResultMatchers.status().isNoContent());
}
}
}
Do you have any #ComponentScan("...") annotation active on your #SpringBootApplication?
As described in Spring Boot Reference Documentation:
Another source of confusion is classpath scanning. Assume that, while you structured your code in a sensible way, you need to scan an additional package. Your application may resemble the following code:
#SpringBootApplication
#ComponentScan({ "com.example.app", "com.example.another" })
public class MyApplication {
// ...
}
Doing so effectively overrides the default component scan directive with the side effect of scanning those two packages regardless of the slice that you chose. For instance, a #DataJpaTest seems to suddenly scan components and user configurations of your application. Again, moving the custom directive to a separate class is a good way to fix this issue.
One solution is to create a seperate #Configuration that is annotated with #ComponentScan. When creating a #WebMvcTest the configuration (and its component scan is ignored).
#Configuration
#ComponentScan("com.example.another")
public class DbConfig {
}
This usually happens when you have explicit #ComponentScan annotation on the spring boot main application class.
The #ComponentScan annotation suppresses the default component scanning mechanism that happens with #Webmvctest where it scans up the package hierarchy and apply excludeFilters to find only controller and its related classes.
When you mock your bean using #MockBean annotation you should define what that mocked bean should do when you call it's method, you usually do this using when in Mockito. In your case it can be done this way:
#ActiveProfiles("test")
#WebMvcTest
class MyControllerTest {
#MockBean
MyControllerDependency dependency;
#Autowired
MockMvc mockMvc;
#Test
void test_something() throws Exception {
when(dependency.sample()).thenReturn("Hello, Mock");
mockMvc.perform(get("/api/test/restpoint")
.accept(MediaType.APPLICATION_JSON))
.andDo(print())
.andExpect(status().isOk());
}
}
At this line:
when(dependency.sample()).thenReturn("Hello, Mock");
Instead of dependency.sample() you should put whatever method of MyControllerDependency class your controller call when you send a GET request to /api/test/restpoint path and with thenReturn("Hello, Mock") you define what is the mocked output of that method (when it gets called by your controller in your Unit test).

NUnit 5 Spring MVC test NoSuchBeanDefinitionException for Autowired dependency in submodule

I have a project with two submodules; one is the data access layer the other is the API service.
The data access module uses JOOQ and an autowired DSLContext in a service class. Also, I'm using JUnit 5, and Spring Boot 2.2.4.
The QueryService class in the data access module has a member like #Autowired private DSLContext dsl;
The test class is set up like this:
#SpringBootTest
public class MyServiceTests {
#Autowired
QueryService service;
#Autowired
private DSLContext dsl;
#Test
public void TestDoSomething() throws Exception {
service.selectBusinessEntityRelatedByBusinessEntity("C00001234", mockAuth);
}
}
The tests in this module run correctly. Configuration is read from the application.yaml, and autowire injects either real services or a mock into both my QueryService and the local dsl.
The API service is a different story. If I use the #SpringBootTest annotation with no MVC I can successfully get the tests to inject a local DSLContext with configuration from the application.yaml. Test set up similar to this:
#SpringBootTest
public class CustomersControllerTests {
#Autowired
private Gson gson;
#Autowired
DSLContext dsl;
#Test
public void addCustomerTest() {
}
What I need though is to use #WebMvcTest so that MockMvc is initialized but switching to #WebMvcTest causes injection to fail in the service class implemented in the data access module. The injection fails to find the DSLContext bean within the query service class. I set up the test like this:
#WebMvcTest
public class CustomersControllerTests {
#Autowired
private MockMvc mockMvc;
#Autowired
private Gson gson;
private static final String testSub = "329e6764-3809-4e47-ac48-a52881045787";
#Test
public void addCustomerTest() {
var newCustomer = new Customer().firstName("John").lastName("Doe");
mockMvc.perform(post("/customers").content(gson.toJson(newCustomer)).contentType(MediaType.APPLICATION_JSON)
.with(jwt().jwt(jwt -> jwt.claim("sub", testSub)))).andExpect(status().isNotImplemented());
}
This is the actual error:
2020-02-25 18:14:33.655 WARN 10776 --- [ main] o.s.w.c.s.GenericWebApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'customersController': Unsatisfied dependency expressed through field '_customersService'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'customersService': Unsatisfied dependency expressed through field '_queryService'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'queryService': Unsatisfied dependency expressed through field '_dsl'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.jooq.DSLContext' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {#org.springframework.beans.factory.annotation.Autowired(required=true)}
So, I know the test application configuration is correct because it works when not using MVC annotation. Also, I can create a DSLContext in the API project tests and I can actually run the API service outside the test.
So, why cant the DSLContext be found when using the MVC test setup?
This might be because #WebMvcTest fully disables Spring Boot's Autoconfiguration and only scans in #Controllers and a few other select classes, that you need for your ..well...MVC tests..
The Spring documentation recommends doing this in your case:
If you are looking to load your full application configuration and use MockMVC, you should consider #SpringBootTest combined with #AutoConfigureMockMvc rather than this annotation.

Spring boot #SpyBean tries to instantiate a new bean instead of spying the one in context

I'm trying to unit-testing a Spring batch job inside Spring boot using JUnit.
I wrote this test class where I want to spy the bean ItemReader :
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment=WebEnvironment.NONE)
#ActiveProfiles({"dev", "batch", "test-jobs"})
public class BatchJobTest {
#Autowired
private JobLauncherTestUtils jobLauncherTestUtils;
private #Autowired #Qualifier("contactDownloadAckJob") Job contactDownloadAckTaskJob;
#SpyBean
private ItemReader<CrsOscContact> reader;
#Test
public void testJob() throws Exception {
given(this.reader.read()).willReturn(new CrsOscContact());
//... blah blah blah
}
}
When I run this test, it seems that the #SpyBean annotation does not do its job, that should be proxying the ItemReader bean that's already present in the context, and so I obtain the (correct) exception because, as per definition, if the bean is not found it tries to instantiate a new bean of that type (and I have specified an interface) :
org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.batch.item.ItemReader]: Specified class is an interface
I'm pretty sure that the bean (of ItemReader type) is already in the context because :
debugging, I see that the target bean instantiation is correctly processed
if I change the #SpyBean annotation to an #Autowired annotation, the instance of previous point is correctly injected
Any hint? Thank you
It was a Spring Core issue, now fixed.
https://github.com/spring-projects/spring-boot/issues/7625
https://jira.spring.io/browse/SPR-15011

Spring Test + Mockito.mock - Spring fails because it tries to load the mocked bean #Autowired dependencies

I can't find out why the following simple scenario is failing: I have a Spring application with a filter that loads a Spring bean from the application context:
public class MyFilter implements Filter{
private IPermissionService permissionService;
public void init(FilterConfig filterConfig) throws ServletException {
WebApplicationContext ac = null;
try{
ac = WebApplicationContextUtils.getRequiredWebApplicationContext(filterConfig.getServletContext());
permissionService = ac.getBean(PermissionServiceImpl.class);
PermissionServiceImpl has an #Autowired attribute dataSource so in my TestNG test, I mock it in the Spring applicationContext:
#Configuration
public class MyFilterSpringTestConfig{
#Bean
public IPermissionService permissionService(){
return Mockito.mock(PermissionServiceImpl.class);
}
MyTest:
#Test
#WebAppConfiguration
#ContextConfiguration(classes=MyFilterSpringTestConfig.class)
public class MyFilterSpringTest extends BaseSpringFilterTest{
...
The problem is that on Spring initialization I get an exception complaining that PermissionServiceImpl's dataSource dependency is not satisfied. Since I wrapped it with a mock, why is it still failing? How could I fix it?
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [javax.sql.DataSource] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {#org.springframework.beans.factory.annotation.Autowired(required=true), #org.springframework.beans.factory.annotation.Qualifier(value=myDataSource)}
When mocking a class using Mockito (or any other mocking framework) that class is still an instance of the original class. With that comes that it also contains all the annotations and class information with it.
So when you create a mock of the class it still detects all annotations on it and tries to full fill that. I.e. #Autowire other instances.
Either don't use auto wiring or don't mock the class but the interface (which doesn't contain that information).

Resources