Testing Spring Boot Actuator endpoint without starting full application - spring

My Spring Boot application is configured with a datasource and exposes the Spring Actuator health and prometheus metrics.
application.yml
spring:
datasource:
driver-class-name: org.mariadb.jdbc.Driver
username: ${db.username}
password: ${db.password}
url: jdbc:mariadb://${db.host}:${db.port}/${db.schema}
management:
endpoints:
web:
exposure:
include: 'health, prometheus'
When starting the application, /actuator/prometheus delivers a response containing metrics. Now I'd like to write a very basic test (JUnit 5) for the prometheus endpoint. This is how it looks at the moment:
Test Class
#SpringBootTest
#ExtendWith(SpringExtension.class)
#AutoConfigureMockMvc
public class HealthMetricsIT {
#Autowired
private MockMvc mockMvc;
#Test
public void shouldProvideHealthMetric() throws Exception {
mockMvc.perform(get("/actuator/prometheus")).andExpect(status().isOk());
}
}
However I'm running into two issues here and I don't know yet how to solve them.
Issue 1
With this setup, the test seems to bring up the whole application, thus trying to connect to a running database.
The test won't start properly because the datasource properties prefixed with db are not provided.
How can I start this test without bringing up the database connection?
Issue 2
Even if my local database is running and I provide all db properties, the test fails. This time because I get a HTTP 404 instead of a 200.

As MockMvc is for testing Spring MVC components (your #Controller and #RestController) I guess the auto-configured mocked Servlet environment you get with #AutoConfigureMockMvc won't contain any Actuator endpoints.
Instead, you can write an integration test that doesn't use MockMvc and rather starts your embedded Servlet container.
#SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
// #ExtendWith(SpringExtension.class) can be omitted with recent Spring Boot versions
public class HealthMetricsIT {
#Autowired
private WebTestClient webTestClient; // or TestRestTemplate
#Test
public void shouldProvideHealthMetric() throws Exception {
webTestClient
.get()
.uri("/actuator/health")
.exchange()
.expectStatus().isOk();
}
}
For this test, you have to make sure that all your infrastructure components (database, etc.) are available that are required upon application startup.
Using Testcontainers, you can provide a database for your integration test with almost no effort.

Related

Integration testing with spring declarative caching

I'm trying to write integration tests for a Spring Boot 2 Application.
One test should test updating a value via REST.
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#AutoConfigureTestEntityManager
#Transactional
public class TenantEndpointIT {
#Autowired
private TestRestTemplate template;
#Autowired
private TestEntityManager entityManager;
#Test
public void nok_updateValueForbidden() {
}
}
Now, I thought the cleanest way was to create the value with the TestEntityManager in the #Before method, then test the REST endpoint in the actual test.
But the service called by the REST Endpoint is annotated with Spring Caching annotations. So the test fails if I do that. I could use the service directly or make a second REST call. That creates problems with other tests using the same Value, because even if the DB is rolled-back, the cache seems to contain the value. (Now I'm using #DirtiesContext).
My question is, how do you correctly integration test services with #Cachable?
Is there a way to get the Cache and explicitly put/remove?
I tried autowiring a CacheManager, but it won't find one and fails.
If you add #AutoConfigureCache on your test, it will override whatever cache strategies you've defined in your app by a CacheManager that noops. That's pretty useful if you want to make sure that cache doesn't interfere with your tests.

Testing servlets with spring beans and embedded jetty

What is the best way to setup embedded jetty to use Spring test context for unit tests?
We are having problems testing legacy servlets which were sprinkled up with Spring beans like this:
public class MyServlet extends HttpServlet {
#Autowired
private MachineDao machineDao;
void init() {
SpringBeanAutowiringSupport.processInjectionBasedOnServletContext(this, this.getServletContext());
}
...
We are using embedded jetty (v9.2) to start Server with a particular servlet to test in spring test.
#ClassRule
public static TestServer server = new TestServer(MyServlet.class);
Problem is that spring test context is not accessible in jetty so processInjectionBasedOnServletContext(...) fails.
We also want to test that .jsp is rendered correctly, so using just new MyServlet() and MockHttpServletRequest & MockHttpServletResponse approach is not really a viable option.
We are using Spring 4.11 (not MVC) with java config & annotations.
Any help much appreciated!

Spring Annotation #WebMvcTest does not work in an app that has Jpa repositories

I have a Spring App that uses JPA repositories (CrudRepository interfaces). When I try to test my controller using the new Spring test syntax #WebMvcTest(MyController.class), it fails coz it tries to instantiate one of my service class that uses JPA Repository, does anyone has any clues on how to fix that? The app works when I run it.
Here is the error:
***************************
APPLICATION FAILED TO START
***************************
Description:
Parameter 0 of constructor in com.myapp.service.UserServiceImpl required a bean of type 'com.myapp.repository.UserRepository' that could not be found.
Action:
Consider defining a bean of type 'com.myapp.repository.UserRepository' in your configuration.
According to the doc
Using this annotation will disable full auto-configuration and instead apply only configuration relevant to MVC tests (i.e. #Controller, #ControllerAdvice, #JsonComponent Filter, WebMvcConfigurer and HandlerMethodArgumentResolver beans but not #Component, #Service or #Repository beans).
This annotion only apply on the Spring MVC components.
If you are looking to load your full application configuration and use MockMVC, you should consider #SpringBootTest combined with #AutoConfigureMockMvc rather than this annotation.
I was able to unit test a Rest Controller by implementing junit 5 and using #SpringJUnitConfig along with #WebMvcTest. I am using Spring Boot 2.4.5 and this is my example:
#SpringJUnitConfig
#WebMvcTest(controllers = OrderController.class)
class OrderControllerTest {
#Autowired
private MockMvc mockMvc;
// This is a Mock bean of a Spring Feign client that calls an external Rest Api
#MockBean
private LoginServiceClient loginServiceClient;
// This is a Mock for a class which has several Spring Jpa repositories classes as dependencies
#MockBean
private OrderService orderService;
#DisplayName("should create an order")
#Test
void createOrder() throws Exception {
OrderEntity createdOrder = new OrderEntity("123")
when(orderService.createOrder(any(Order.class))).thenReturn(createdOrder);
mockMvc.perform(post("/api/v1/orders").contentType(MediaType.APPLICATION_JSON).content("{orderId:123}"))
.andExpect(status().isCreated())
.andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8))TODO: here it will go the correlationId
.andExpect(jsonPath("$.orderId").value("123"));
}
}
Please only use #SpringBootTest when you are implementing integration tests.
I faced this same problem. Using #SpringBootTest and #AutoConfigureMockMvc worked perfectly for me.

How to ensure load time weaving takes place for Eclipselink when using SpringBootTest with other tests running beforethe Spring one

I'm using Spring Rest Docs to generate documentation for my REST services. This involves running unit(strictly integration) tests that run against a live Spring Boot Container that is kicked off by the test. The test class looks like this:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest(classes = MySpringConfiguration.class)
#WebAppConfiguration
public class ApiDocumentation {
private MockMvc mockMvc;
#Rule
public final JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation("target/generated-snippets");
#Autowired
private WebApplicationContext context;
#Autowired
private ObjectMapper objectMapper;
#Before
public void setUp() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(documentationConfiguration(this.restDocumentation))
.build();
}
#Test
public void testSomething() throws Exception {
}
}
The application uses JPA with EclipseLink for the EntityManager implementation.
When I run the test standalone in my IDE or as the only test present when I run a Maven build using the maven-surefire-plugin everything works fine.
However it's not the only test I want to run in the suite. As soon as I run other tests in the suite I come across the issue mentioned here, namely
"Spring's agent does not initialize the persistence context until the application accesses the Spring context. If the application has already triggered the loading of the persistent class before accessing the Spring context, weaving will not occur."
and get errors like this:
Exception Description: The method [_persistence_set_someField_vh] or [_persistence_get_someField_vh] is not defined in the object [mypackage.MyEntity].
So what do people normally do to get around this ? Run SpringBootTest classes in a different module to unit tests that access entities ?
As far as I concerned problem caused by dynamic weaving, if you make it static it should work proper. Possibly it could help you
Another solution could be to disable dynamic weaving in that particular test using eclipselink.weaving JPA property.
See this question and its answers: #SpringBootTest interferes with EclipseLink dynamic weaving

Strategy for unit testing a Spring Cloud Service

Given the following Spring Cloud setup: A data-service with access to a database, an eureka-service to handle service registry and discovery and a third service business-service which will be one of various services which encapsulate business cases.
Unit testing the data-service is no problem, I just turn off eureka via
eureka.client.enabled=false
and use an in-memory database for my tests.
To access the data-service from business-service, I'm using an #FeignClient("data-service") annotated interface named DataClient which is #Autowired where needed. The service is discovered by Eureka, if both are running. This works fine for a production-like setup with all services running.
But now I want to unit test some features of my business-service. It wouldn't be a problem to start a test service with
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#IntegrationTest("server.port:0")
#SpringApplicationConfiguration(classes = Application.class)
like I'm doing in data-service. The problem is the Eureka-dependent discovery of my FeignClient... So my testclass crashes, because the autowiring my DataClient-instance doesn't work.
Am I abled to tell Spring to use a faked instance of DataClient just for my tests? Or is the only way to get my tests running an accessible, running instance of data-service and my Eureka server?
1, first create config bean, let the discovery client and feignclient only work when "eureka.enabled" is true
#Configuration
#EnableDiscoveryClient
#EnableFeignClients
#ConditionalOnProperty(name = "eureka.enabled")
public class EurekaConfig {
}
2, disable the eureka config for test profile, so in application-test.yml
eureka:
enabled: false
3, my project is build by maven, so i create a implement for my feign client interface, for example:
#Service
public class DataServiceImpl implements DataService {}
after this, when you run test in unit test with
#WebAppConfiguration
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = Application.class)
#IntegrationTest({"server.port=0", "management.port=0", "spring.profiles.active=test"})
public abstract class AbstractIntegrationTests {}
the fake service will inject in to spring context.
Or for normal unit test case, you can just need mockito mock the service class and use set method or construct method inject the mock object in your class
My first attempt crashed because of another bug... So it works fine with a #Configuration annotated class Conf which creates an fake implementation of DataClient like this:
#Bean
#Primary
public DataClient getDataClient() {
...
}
Added to my test via
#SpringApplicationConfiguration(classes = {Application.class, Conf.class})
the tested service instance uses the fake implementation correctly.
Adding on Yunlong's answer on annotating on a separate configuration class.
If the configuration class is placed under a different package than the root package, you will need to specify the "basePackages" for the #EnableFeignClients to scan for the annotated #FeignClient component.
com.feign.client.FeignClient.class
#FeignClient(value = "${xxxx}")
public interface FeignClient {
}
com.feign.config.EurekaConfig.class
#Configuration
#EnableFeignClients(basePackages = {"com.feign.client"})
#EnableEurekaClient
#ConditionalOnProperty(name = "eureka.enabled")
public class EurekaClientConfig {
}
Ps. I couldnt comment to the original reply so I created a new answer.

Resources