MockBean in a Spring boot test not getting autowired into the component being tested - spring-boot

I have a spring boot Service using an autowired ApplicationEventPublisher:
#Service("ILiveOrderHandlerImpl")
#NoArgsConstructor
#RequiredArgsConstructor
public class ILiveOrderHandlerImpl implements ILiveOrderHandler {
#Autowired
private ApplicationEventPublisher applicationEventPublisher;
In the spring boot integration test I have specified #MockBean for ApplicationEventPublisher.
#RunWith(SpringRunner.class)
#SpringBootTest(classes = {StockTraderApplication.class},
class ILiveOrderHandlerImplTest {
#Autowired
#Qualifier("ILiveOrderHandlerImpl")
private ILiveOrderHandler liveOrderHandler;
#MockBean
ApplicationEventPublisher mockApplicationEventPublisher;
While debugging I find that the field isn't mocked but it is the real object (org.springframework.web.context.support.GenericWebApplicationContext). In the test, the field is the mocked object that fails "verify()" test because it was never autowired. Why does this happen?

Related

PostConstruct and test

I use spring boot 3
Main spring boot class
#EnableTransactionManagement
#SpringBootApplication
#Slf4j
public class FlexApplication{
private final ApplicationParameterManager appParamManager;
public FlexApplication(ApplicationParameterManager appParamManager) {
this.appParamManager = appParamManager;
}
#PostConstruct
public void init(){
}
....
}
#Service
#Slf4j
public class ApplicationParameterManager{
....
}
Basic test
#AutoConfigureTestDatabase(replace= AutoConfigureTestDatabase.Replace.NONE)
#DataJpaTest
public class ListUserRepositoryTest {
#Autowired
private ListUserRepository repository;
#Test
public void getListUserByUserType(){
String typeUser = "CETEST";
Pageable page = Pageable.ofSize(10);
Page<ListUser> pageListUser = repository.findAllByTypeUser(typeUser, page);
assertThat(pageListUser.getContent().size() > 5 ).isTrue();
}
}
Otherwise this test, application run well
I get this error
Parameter 0 of constructor in com.acme.FlexApplication required a bean
of type 'com.acme.parameter.ApplicationParameterManager' that could
not be found.
I think it is not related to version of Spring Boot.
As you're using #DataJpaTest , your bean is not created
Spring Docs:
#DataJpaTest can be used if you want to test JPA applications. By
default it will configure an in-memory embedded database, scan for
#Entity classes and configure Spring Data JPA repositories. Regular
#Component beans will not be loaded into the ApplicationContext.
Solution would be to use #SpringBootTest instead of #DataJpaTest if your test is not really a JPA test.
Also, still using #DataJpaTest you could add #Import(ApplicationParameterManager.class) to your test class
When using #DataJpaTest, you are not creating the whole spring context as when you run the application normally but you only create the beans responsible for data access layer.
In order to run your tests properly, you need to provide a mocked bean of type ApplicationParameterManager.
The easiest way to do it is by utilizing #MockBean annotation.
So, to make your tests work, edit the test in the following way.
#AutoConfigureTestDatabase(replace= AutoConfigureTestDatabase.Replace.NONE)
#DataJpaTest
public class ListUserRepositoryTest {
#MockBean
private ApplicationParameterManager applicationParameterManager;
#Autowired
private ListUserRepository repository;
#Test
public void getListUserByUserType(){
String typeUser = "CETEST";
Pageable page = Pageable.ofSize(10);
Page<ListUser> pageListUser = repository.findAllByTypeUser(typeUser, page);
assertThat(pageListUser.getContent().size() > 5 ).isTrue();
}
}
That way, the spring context will include a mocked bean of your required dependency.
Take a look at #MockBean java doc for more information. https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/test/mock/mockito/MockBean.html
If you prefer to run the whole spring context in order to perform full integration tests, take a look at #SpringBootTest annotation.
#DataJpaTest should be used when you want to test data access layer in isolation.

How to #Autowired a test controller invoked by WebTestClient in #WebFluxTest?

I have a Controller just for tests with dummy apis
#RestController
public class TestController {
#Autowired
private org.springframework.cloud.sleuth.Tracer tracer;
#GetMapping("/trace")
public Mono<String> traceTest() {
...
}
}
Here's my test
#WebFluxTest(controllers = TestController.class)
public MyTest {
#Autowired
private WebTestClient webClient;
#Test
public void testTrace() {
webClient.get().uri("/trace")...
}
}
When I try to run this, my Tracer is not Autowired.
Of course, if I change my test to a #SpringBootTest, it all works
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public MyTest {
...
}
But I'd like to avoid autowiring my entire spring application. How can I get TestController to be auto-configured?
The #WebFluxTest annotation populates a Spring TestContext with only a subset of the relevant beans:
#WebFluxTest auto-configures the Spring WebFlux infrastructure and
limits scanned beans to #Controller, #ControllerAdvice,
#JsonComponent, Converter, GenericConverter, WebFilter, and
WebFluxConfigurer. Regular #Component and #ConfigurationProperties
beans are not scanned when the #WebFluxTest annotation is used.
#EnableConfigurationProperties can be used to include
#ConfigurationProperties beans. from Spring Boot Documentation
Your Tracer won't be part of this TestContext out-of-the-box.
For your #WebFluxTest you can provide a mocked version of this bean with #MockBean:
#WebFluxTest(controllers = TestController.class)
public MyTest {
#Autowired
private WebTestClient webClient;
#MockBean
private Tracer tracer;
#Test
public void testTrace() {
webClient.get().uri("/trace")...
}
}
... and if you want to test the full integration I would rather use #SpringBootTest.

Write Unit test in SpringBoot Without start application

Am developing MicroServices in springBoot. Am writing unit test for Service and DAO layer. When I use #SpringBootTest it starting application on build. But It should not start application
when I run unit test. I used #RunWith(SpringRunner.class), But am unable to #Autowired class instance in junit class. How can I configure junit test class that should not start application and how to #Autowired class instance in junit class.
Use MockitoJUnitRunner for JUnit5 testing if you don't want to start complete application.
Any Service, Repository and Interface can be mocked by #Mock annotation.
#InjectMocks is used over the object of Class that needs to be tested.
Here's an example to this.
#RunWith(MockitoJUnitRunner.class)
#SpringBootTest
public class AServiceTest {
#InjectMocks
AService aService;
#Mock
ARepository aRepository;
#Mock
UserService userService;
#Before
public void setUp() {
// MockitoAnnotations.initMocks(this);
// anything needs to be done before each test.
}
#Test
public void loginTest() {
Mockito.when(aRepository.findByUsername(ArgumentMatchers.anyString())).thenReturn(Optional.empty());
String result = aService.login("test");
assertEquals("false", result);
}
With Spring Boot you can start a sliced version of your application for your tests. This will create a Spring Context that only contains a subset of your beans that are relevant e.g. only for your web layer (controllers, filters, converters, etc.): #WebMvcTest.
There is a similar annotation that can help you test your DAOs as it only populates JPA and database relevant beans (e.g. EntitiyManager, Datasource, etc.): #DataJpaTest.
If you want to autowire a bean that is not part of the Spring Test Context that gets created by the annotatiosn above, you can use a #TestConfiguration to manually add any beans you like to the test context
#WebMvcTest(PublicController.class)
class PublicControllerTest {
#Autowired
private MockMvc mockMvc;
#TestConfiguration
static class TestConfig {
#Bean
public EntityManager entityManager() {
return mock(EntityManager.class);
}
#Bean
public MeterRegistry meterRegistry() {
return new SimpleMeterRegistry();
}
}
}
Depending your test setup, if you don't want to autowire a mock but the "real thing", You could simply annotate your test class to include exactly the classes you need (plus their transitive dependencies if necessary)
For example :
#SpringJUnitConfig({ SimpleMeterRegistry.class })
or
#SpringJUnitConfig
#Import({ SimpleMeterRegistry.class })
or
#SpringJUnitConfig
#ContextConfiguration(classes = { SimpleMeterRegistry.class })
See working JUnit5 based samples in here Spring Boot Web Data JDBC allin .

How to have bean of repository interface of spring data jpa during run of test cases

We have written below 2 classes
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = {TestConfig.class})
public class AbcFilterTest {
#Autowired
private AbcUtils abcUtils;
#Autowired
private AbcRepository abcRepository;
#Test
public void testFilter() {
MockHttpServletRequest httpServletRequest = new MockHttpServletRequest();
MockHttpServletResponse httpServletResponse = new MockHttpServletResponse();
MockFilterChain mockChain = new MockFilterChain();
}
}
#Configuration
#ComponentScan(basePackageClasses = {AbcUtils.class, AbcRepository.class,})
public class TestConfig {
}
For running a test class, i need instance of both AbcUtilsclass and AbcRepository interface(extending CurdRepository) which are autowired in the class i am testing.1st one has #Component while 2nd has #Repositry on top of it.As you can see,in my test class I have autowired both util and repository class with component scan as above code.In real time spring framework creates implementaion class for the repository interface but On running test case,I am getting below error
Caused by: org.springframework.beans.BeanInstantiationException:
Failed to instantiate [com.a.b.c.persistence.AbcRepository]: Specified
class is an interface.
Kindly suggest how to make this test class work. Please note that we are not using Spring Boot and Mockito.We are doing integration testing.
We are using Spring rest and Spring Data JPA with hibernate.
Scanning AbcUtils.class and AbcRepository.class is not enough if you want to write something in the database. You need to scan the configuration that contains your DataSource definition and related config. Since the tests will be instantiated like a Spring bean too, the test class need to implement the ApplicationContextAware interface:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = {"/config.xml"})
public class TestClass implements ApplicationContextAware{
#Autowired
Repository repository;
//....
}

Spring Web Service Test with MongoDB

I'm writing service tests for my Spring Boot Web application that acts as an interface to MongoDB. Ideally, my service test will test each component of my Spring application before finally hitting a Mocked MongoTemplate. The following code uses a MockMvc to hit my web endpoints.
#RunWith(SpringRunner.class)
#WebMvcTest(MyController.class)
#AutoConfigureDataMongo
public class MyControllerServiceTest {
#Autowired
private MockMvc mvc;
#Autowired
private MongoTemplate mongoTemplate
#SpyBean
private MyMongoRepository myMongoRepository;
#Test
public void createTest() {
MyObject create = new MyObject()
given(this.myMongoRepository.insert(create));
this.mvc.perform(post("localhost:8080/myService")...)...;
}
}
MyController contains an #Autowired MyMongoRepository, which in turn implements MongoRepository and necessitates a mongoTemplate bean. This code executes properly only if a running MongoDB instance can be found (this example is more of an integration test between my service and MongoDB).
How can I mock out MongoTemplate while still using my MockMvc?
You need to add the following line to your test unit:
#MockBean
private MongoTemplate mongoTemplate;
For example, your class should look like this:
#RunWith(SpringRunner.class)
#WebMvcTest(MyController.class, excludeAutoConfiguration = EmbeddedMongoAutoConfiguration.class)
public class MyMvcTests {
#Autowired
private MockMvc mvc;
#MockBean
private MyRepository repository;
#MockBean
private MongoTemplate mongoTemplate;
#Test
public void someTest() {}
}
You can find a complete Spring Boot application that include Integration and Unit tests here.
I think a better approach to test would be to test your web layer(controller) and your service layer separately.
For testing your web layer you can use MockMvc and you can mock your service layer.
For testing your service layer which in turn talks to mongo, you can use a Fongo and nosqlunit.
Some examples here
https://arthurportas.wordpress.com/2017/01/21/sample-project-using-spring-boot-and-mongodbfongo-and-test-repository-with-nosqlunit/
https://github.com/JohnathanMarkSmith/spring-fongo-demo

Resources