How to correctly setup Spring Boot 2.2 REST full integration test with JUnit5 - spring-boot

I have simple controller, facade, set of services and repository for my H2 DB for Customer entity. It works fine but I need an E2E integration test with whole application context. And with JUnit5.
I've tried many setups, many times got errors like found multiple declarations of #BootstrapWith for test. Finally I made it work, but there is an issue with Spring JPA CustomerRepository which is an interface. I've never tried to use it like this so I have no experiance with it. But simply speaking, it won't create any bean of this type since it's just an interface. As the spring boot application, it works this way, but in the integration test it won't.
The error is still the same: Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [xxx.CustomerRepository].
This is my test setup:
#ExtendWith(SpringExtension.class)
#AutoConfigureMockMvc
#ContextConfiguration(classes = {
CustomerController.class,
CustomerFacade.class,
CustomerTerminationService.class,
CustomerRepository.class
})
#WebMvcTest(CustomerController.class)
#Import(CustomerRepository.class)
class CustomerFullIntegrationTest {
#Autowired
private MockMvc mockMvc;
#Test
void getCustomer() throws Exception {
mockMvc.perform(MockMvcRequestBuilders
.get("/customer/1")
.accept(MediaType.APPLICATION_JSON))
.andDo(print())
.andExpect(status().isOk())
.andExpect(jsonPath("$.lastName").exists())
.andExpect(jsonPath("$.products").isNotEmpty());
}
}
This is my repository:
public interface CustomerRepository extends JpaRepository<Customer, Long> {}
This is my Application:
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(ProfApplication.class, args);
}
}
Controller, facade, services are irrelevant.
Can you tell me, what exactly am I doing wrong?

Related

How to exclude Mongo spring data configuration only for tests

I am doing a simple sample project with Spring boot and data.
#Configuration
public class MongoConfig {
private static final Logger LOGGER = LoggerFactory.getLogger(MongoConfig.class);
#Value("${mongo.uri}")
private String mongoUri;
public #Bean MongoClient mongoClient() {
LOGGER.info(" creating connection with mongodb with uri [{}] ", mongoUri);
return MongoClients.create(mongoUri);
}
}
This works fine and connects to mongo on startup. However, the tests also pick this up in autoscan. What is the best practice to make sure that mongo config gets excluded for tests?
If I add #WebMvcTest to the tests, it works. But not all tests will be mvc tests. I might be testing a utility class.
If I try using profiles, it gives my an error java.lang.IllegalStateException: The following classes could not be excluded because they are not auto-configuration classes: ...MongoConfig
#SpringBootTest
#ActiveProfiles("test")
class ApplicationTests {
#Test
void contextLoads() {
}
}
Please tell me a repeatable practice as I will be using it for all my tests.
The simplest way of doing it is to exclude your configuration bean when your test profile is active
#Profile("!test")
#Configuration
public class MongoConfig {
...
}
Here you tell to the BeanFactory to not create this bean if the profile test is present
Ref: https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/annotation/Profile.html

#WebFluxTest throws org.springframework.boot.context.properties.bind.Binder has not been registered error on Jenkins pipeline

I've built a Spring WebFlux application (Annotated Controller model) that integrates with Google Cloud Services with the below mentioned library versions. I've written a test for Controller class and when it is run on my local machine it runs without any issue. But when the same test is run on Jenkins pipeline it throws Binder has not been registered error.
Spring Boot Version - 2.5.5
Spring Cloud GCP Version - 2.0.4
The error that gets printed on the Jenkins console is as below
[ERROR] com.test.controller.ControllerTest.shouldPostRequestToEndpoint Time elapsed: 0.001 s <<< ERROR!
java.lang.IllegalStateException: Failed to load ApplicationContext
Caused by: java.lang.IllegalStateException: org.springframework.boot.context.properties.bind.Binder has not been registered
The most confusing thing is this issue happens only on Jenkins instance and not on my local machine and it makes it very difficult to debug. I'm not sure on why it appears that way. Also, not sure how to register any Binder here. If I could get any help/direction on where the issue might be or how to debug, it would be very helpful.
Sample Controller and ControllerTest class can be found below.
Controller.java
#RestController
public class Controller {
private final Service service;
#Autowired
public Controller(Service service) {
this.service = service;
}
#PostMapping("/test")
public Mono<String> test() {
return service.test(LocalDateTime.now())
.then(Mono.just("SUCCESS"));
}
}
ControllerTest.java
#WebFluxTest(controllers = Controller.class)
public class ControllerTest {
#MockBean
Service service;
#Autowired
WebTestClient webTestClient;
#Test
public void shouldPostRequestToEndpoint() {
given(service.test(any(LocalDateTime.class))).willReturn(empty());
webTestClient
.post()
.uri("/test")
.exchange()
.expectStatus().isOk()
.expectBody(String.class);
}
}
Even if you guys point me towards identifying what Binder needs to be registered, it would be great.
Thanks in advance!
It looks like spring context can't load. Is your JUnit version different between local and CI? If you use JUnit4, try adding #RunWith(SpringRunner.class) to your test.

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 .

Testing Spring Data Rest

I want to test a Spring boot 2 respository as rest controller app.
App is working well from browser ( http://localhost:8080/api/v1/ehdata ), but I cannot find an example how can I test it with Spring test environment. Very important, there are no RestControllers and Services, only Repositories annotated like this:
#RepositoryRestResource(path = EhDataRepository.BASE_PATH,
collectionResourceRel = EhDataRepository.BASE_PATH)
public interface EhDataRepository extends
PagingAndSortingRepository<EhData, Long> {
public static final String BASE_PATH="ehdata";
}
I tried with this test, but responses was empty, and status code was 404:
#RunWith(SpringRunner.class)
#SpringBootTest
#WebMvcTest(EhDataRepository.class)
public class RestTest extends AbstractRestTest {
#Autowired MockMvc mvc;
#Test
public void testData() throws Exception {
mvc.perform(get("/api/v1/ehdata")
.accept(MediaTypes.HAL_JSON_VALUE))
.andDo(print())
.andExpect(status().isOk())
.andExpect(header().string(HttpHeaders.CONTENT_TYPE,
MediaTypes.HAL_JSON_VALUE+";charset=UTF-8")
.andReturn();
}
}
thx,
Zamek
You will need to mock the output from the respository like this based on the method you are trying to test:
#MockBean
private ProductRepo repo;
And then
Mockito.when(this.repo.findById("PR-123")
.get())
.thenReturn(this.product);
this.mvc.perform(MockMvcRequestBuilders.get("/products/{id}", "PR-123")
.contentType(MediaType.APPLICATION_JSON_VALUE))
.andReturn();
Also, remove the server-context-path while calling API in perform() method.

Spring Boot Integration Test Inject Controller Dependencies

I am trying to write an integration test using Spring Boot that tests the transaction logic in one of my controllers.
What the test should do, is the following:
Inject one of my controllers using #Inject
Replace an email dependency in the controllers dependencies with a Mock, to avoid actually sending an email during the integration test.
Call a method of the controller
Assert that the transactions of the called method are properly rolled back when the mail sending mock throws an exception.
Now my problem is that, when the test runs, the controller is injected into my test class but all its dependencies are null. Here is my integration test:
#RunWith(SpringJUnit4ClassRunner.class)
#IntegrationTest
#SpringApplicationConfiguration(App.class)
#WebIntegrationTest
public MyIntegrationTest () {
#Inject MyController controller;
#Before
public void before () {
// replace one particular dependency of controller with a mock
}
#Test
public void testFoo () { ... }
}
Due to the test being an integration test which starts up a full spring web application context, I was expecting that my controller would have all its dependencies already autowired, but that is obviously not the case and instead all dependencies are set to null.
Question: Do I need to use some additional annotations, or setup something in my #Before method? Or am I approaching the problem from a completely wrong side?
Update: Is it possible to test my Spring MVC Layer, without testing via HTTP such as with TestRestTemplate or MockMvc? But by directly
Test with TestRestTemplate instead of injecting the controller itself. Controllers is obviously a spring bean but if you directly inject it in your test class, it wont be able to initialize the context.
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = ExampleStart.class)
public class ExampleTest {
#Autowired
private TestRestTemplate restTemplate;
#Test
public void exampleTest() {
String body = this.restTemplate.getForObject("/", String.class);
assertThat(body).isEqualTo("Hello World");
}
}
ExampleStart.java -> The spring boot starter class
#Configuration
#ComponentScan
#EnableAutoConfiguration
public class ExampleStart extends SpringBootServletInitializer {
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(ExampleStart.class);
}
public static void main(String[] args) {
SpringApplication.run(ExampleStart.class, args);
}
}
Ref : https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-testing.html
But if you want to test service method, you can use #Autowired and call the methods as usual.

Resources