Testing Spring Actuator Info Values - java

I am writing some mockMvc unit tests for my actuator. I currently have one for health, which works fine
class HealthTest {
#Autowired
private Mockmvc mockMvc;
private ResultActions resultActions;
#BeforeEach() throws Exception {
resultActions = mockMvc.perform(get("/actuator/health"));
}
#Test
void shouldReturnOk() throws Exception {
resultActions.andExpect(jsonPath("status", is("UP")));
}
}
This works fine. However, when apply the same logic to "/actuator/info" (literally the exact same as the health class, with only that path changed (with config defined in the application.yml, and I have reviewed this manually, it's there when I run the application) I get a 200 status back, but no JSON, even though the web page itself shows a JSON object. It's like when I run it through this, it gets a blank page back, or the page, but not in json format.
Edit: So the config is in the main/application.yml. When I replicate the config to the test/application.yml, it works. Is there a way to get mvc to point to my main application.yml? As all this really tests is my duplicated test config
Edit 2: Better formatting of comment:
management:
endpoints:
web:
exposure:
include:
- info
info:
application:
name: My application name

/actuator/info provides your customized information. The default is empty information. Therefore, you must create a Spring bean to provide this information, for example:
import java.util.HashMap;
import java.util.Map;
import org.springframework.boot.actuate.info.Info;
import org.springframework.boot.actuate.info.InfoContributor;
import org.springframework.stereotype.Component;
#Component
public class BuildInfoContributor implements InfoContributor {
#Override
public void contribute(Info.Builder builder) {
Map<String, String> data = new HashMap<>();
data.put("version", "2.0.0.M7");
builder.withDetails(data);
}
}
And test:
#SpringBootTest
#AutoConfigureMockMvc
class Test {
#Autowired
private MockMvc mockMvc;
private ResultActions resultActions;
#BeforeEach()
void setUp() throws Exception {
resultActions = mockMvc.perform(MockMvcRequestBuilders.get("/actuator/info"));
}
#Test
void shouldReturnOk() throws Exception {
resultActions.andExpect(jsonPath("version", is("2.0.0.M7")));
}
}

Problem solved. So it turns out the test resources file can't be called application.yml, it needs it's own profile, or it totally overrides the main one.

Related

Java Unit Testing Http Get test user auto generate

I write function in rest to automatically generate user with admin role. Here's the function:
UserController.java
#RestController
#RequestMapping("users")
public class UserController {
#Autowired
private UserRepository userRepo;
#Autowired
private TokenRepository tokenRepo;
#GetMapping("admin")
public String getAdmin () {
JSONObject report = new JSONObject();
String dataAdmin = userRepo.findByUsername("admin");
if(dataAdmin == null) {
User myadmin = new User();
myadmin.setUsername("admin");
myadmin.setFirstname("admin");
myadmin.setLastname("admin");
myadmin.setEmail("admin#admin");
myadmin.setRole("admin");
userRepo.save(myadmin);
report.put("message", "admin generated");
} else {
report.put("message", "admin only generated once");
}
return report.toString();
}
I am trying to follow the instruction from here https://www.springboottutorial.com/unit-testing-for-spring-boot-rest-services.
In Unit Testing Http Get Operation section. I am getting several problem and also trying the different solution until I meet this Unit testing a Get request in a spring project from stackoverflow.
below is the testing script I've make so far.
package blablaaa.order;
import java.util.ArrayList;
import java.util.List;
import org.json.JSONObject;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.RequestBuilder;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import blablaaa.order.controller.UserController;
import blablaaa.order.dao.UserRepository;
import blablaaa.order.model.User;
//#ExtendWith(SpringExtension.class)
//#SpringBootTest
#WebMvcTest(value = UserController.class)
class OrderApplicationTests {
//
#Autowired
private MockMvc mockMvc;
#MockBean
private UserRepository userRepo;
#Test
void contextLoads() throws Exception{
User myadmin = new User();
myadmin.setUsername("admin");
myadmin.setFirstname("admin");
myadmin.setLastname("admin");
myadmin.setEmail("admin#admin");
myadmin.setRole("admin");
List<User> myUser = new ArrayList<>();
myUser.add(myadmin);
RequestBuilder rb = MockMvcRequestBuilders.get("/users/admin").accept(MediaType.APPLICATION_JSON);
MvcResult result = mockMvc.perform(rb).andReturn();
JSONObject expect = new JSONObject();
expect.put("message", "admin generated");
// System.out.println(result.toString());
System.out.println(expect.toString());
// Assertions.assertTrue(result.toString().contains(expect.toString()));
}
}
I dont know, how the testing should be written. any keyword related to this?
[update]
Here's my main:
// OrderApplication.java
#SpringBootApplication
#EnableMongoRepositories("blablaaa.order.dao")
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
}
Here's my program error log
Description:
Field tokenRepo in blablaaa.order.controller.UserController required a bean named 'mongoTemplate' that could not be found.
The injection point has the following annotations:
- #org.springframework.beans.factory.annotation.Autowired(required=true)
Action:
Consider defining a bean named 'mongoTemplate' in your configuration.
The first thing you have to do is to clearly understand what your method getAdmin is doing and secondly clearly identify your dependencies and how they interact in the main method.
To finish, you have to define what you want to test, in your case you have two cases. The if case and the else case.
In your test you must define the behavior of your mock with a when().thenReturn()...
Your test method will look like this:
#WebMvcTest(value = UserController.class)
class OrderApplicationTests {
#Autowired
private MockMvc mockMvc;
#MockBean
private UserRepository userRepo;
#Test
void contextLoads_whenNotNull() throws Exception{
User myadmin = new User();
myadmin.setUsername("admin");
myadmin.setFirstname("admin");
myadmin.setLastname("admin");
myadmin.setEmail("admin#admin");
myadmin.setRole("admin");
when(userRepo).thenReturn(myadmin)
mockMvc.perform(get("/users/admin")
.contentType(MediaType.APPLICATION_JSON))
.andExpectAll(
status().isOk(),
jsonPath("$.message", containsString("admin generated"))
);
}
#Test
void contextLoads_whenIsNull() throws Exception{
when(userRepo).thenReturn(null)
mockMvc.perform(get("/users/admin")
.contentType(MediaType.APPLICATION_JSON))
.andExpectAll(
status().isOk(),
jsonPath("$.message", containsString("admin only generated once"))
);
}
}
For more details: https://www.bezkoder.com/spring-boot-webmvctest/
previously i am laravel user. When i run unit testing, i dont need to run the apps before i can run testing scenario. So i think, the error log tell me that the mongo database is not connected to apps, even the apps itself is not running yet.
So, based on my case, i need to make sure run the apps first, not build. If i only build the apps, the program will remain error.
By default, when you need to build the apps, you need to make sure the apps is already running too. Spring Boot will also run the test case before build the jar file.
first, run the apps
mvn spring-boot:run
then,
you can run only the test case
mvn test
or build your apps
mvn clean package

Spring can't resolve dependencies with an internal class

today I've met online another poor soul learning Spring. I decided I'll help them. Story as old as Spring, a missing bean in unit tests. I made a quick fix, I put a configuration with the missing bean and it worked, seemed like everything was fine.
#Configuration
class Config {
#Bean
HelloService getHelloService() {
return new HelloService();
}
}
#ExtendWith(SpringExtension.class)
#WebMvcTest(HelloController.class)
#Import({Config.class})
class HelloControllerIntTest {
#Autowired
private MockMvc mvc;
#Test
void hello() throws Exception {
RequestBuilder request = get("/hello");
MvcResult result = mvc.perform(request).andReturn();
assertEquals("Hello, World", result.getResponse().getContentAsString());
}
#Test
public void testHelloWithName() throws Exception {
mvc.perform(get("/hello?name=Dan"))
.andExpect(content().string("Hello, Dan"));
}
}
On the second thought, polluting the public space with additional and very genericly named class is not a good idea, so I decided to put it inside of the class.
#ExtendWith(SpringExtension.class)
#WebMvcTest(HelloController.class)
#Import({HelloControllerIntTest.Config.class})
class HelloControllerIntTest {
#Configuration
static class Config {
#Bean
HelloService getHelloService() {
return new HelloService();
}
}
#Autowired
private MockMvc mvc;
#Test
void hello() throws Exception {
RequestBuilder request = get("/hello");
MvcResult result = mvc.perform(request).andReturn();
assertEquals("Hello, World", result.getResponse().getContentAsString());
}
#Test
public void testHelloWithName() throws Exception {
mvc.perform(get("/hello?name=Dan"))
.andExpect(content().string("Hello, Dan"));
}
}
To my surprise, it doesn't work, 404 error. I put a breakpoint in the HelloController and it seems the bean is not constructed at all. Also I peeked into the beans definitions and it seems the first version has 91 beans, and the second 88, so we have missing beans over there.
Any ideas what happened here? Why in the second version Spring ignores HelloController?
The reason why this happens is because your Config annotation is looking in a sub-package to find the beans but it can no longer find them.
If you annotate your Config static class with a #ComponentScan("package.of.your.helloController") then your controller it will be found again.

Using #PostConstruct in a test class causes it to be called more than once

I am writing integration tests to test my endpoints and need to setup a User in the database right after construct so the Spring Security Test annotation #WithUserDetails has a user to collect from the database.
My class setup is like this:
#RunWith(value = SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#AutoConfigureMockMvc
#WithUserDetails(value = "email#address.com")
public abstract class IntegrationTests {
#Autowired
private MockMvc mockMvc;
#Autowired
private Service aService;
#PostConstruct
private void postConstruct() throws UserCreationException {
// Setup and save user data to the db using autowired service "aService"
RestAssuredMockMvc.mockMvc(mockMvc);
}
#Test
public void testA() {
// Some test
}
#Test
public void testB() {
// Some test
}
#Test
public void testC() {
// Some test
}
}
However the #PostConstruct method is called for every annotated #Test, even though we are not instantiating the main class again.
Because we use Spring Security Test (#WithUserDetails) we need the user persisted to the database BEFORE we can use the JUnit annotation #Before. We cannot use #BeforeClass either because we rely on the #Autowired service: aService.
A solution I found would be to use a variable to determine if we have already setup the data (see below) but this feels dirty and that there would be a better way.
#PostConstruct
private void postConstruct() throws UserCreationException {
if (!setupData) {
// Setup and save user data to the db using autowired service "aService"
RestAssuredMockMvc.mockMvc(mockMvc);
setupData = true;
}
}
TLDR : Keep your way for the moment. If later the boolean flag is repeated in multiple test classes create your own TestExecutionListener.
In JUnit, the test class constructor is invoked at each test method executed.
So it makes sense that #PostConstruct be invoked for each test method.
According to JUnit and Spring functioning, your workaround is not bad. Specifically because you do it in the base test class.
As less dirty way, you could annotate your test class with #TestExecutionListeners and provide a custom TestExecutionListener but it seem overkill here as you use it once.
In a context where you don't have/want the base class and you want to add your boolean flag in multiple classes, using a custom TestExecutionListener can make sense.
Here is an example.
Custom TestExecutionListener :
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.test.context.TestContext;
import org.springframework.test.context.support.AbstractTestExecutionListener;
public class MyMockUserTestExecutionListener extends AbstractTestExecutionListener{
#Override
public void beforeTestClass(TestContext testContext) throws Exception {
MyService myService = testContext.getApplicationContext().getBean(MyService.class);
// ... do my init
}
}
Test class updated :
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#AutoConfigureMockMvc
#WithUserDetails(value = "email#address.com")
#TestExecutionListeners(mergeMode = MergeMode.MERGE_WITH_DEFAULTS,
value=MyMockUserTestExecutionListener.class)
public abstract class IntegrationTests {
...
}
Note that MergeMode.MERGE_WITH_DEFAULTS matters if you want to merge TestExecutionListeners coming from the Spring Boot test class with TestExecutionListeners defined in the #TestExecutionListeners of the current class.
The default value is MergeMode.REPLACE_DEFAULTS.

Spring Boot Integration Testing with mocked Services/Components

Situation and Problem: In Spring Boot, how can I inject one or more mocked classes/beans into the application to do an integration test? There are a few answers on StackOverflow, but they are focused on the situation before Spring Boot 1.4 or are just not working for me.
The background is, that in the code bellow the implementation of Settings relies on third party servers and other external systems. The functionality of Settings is already tested in a unit test, so for a full integration test I want to mock out the dependency to these servers or system and just provide dummy values.
MockBean will ignore all existing bean definitions and provide a dummy object, but this object doesn't provide a method behavior in other classes that inject this class. Using the #Before way to set the behavior before a test doesn't influence the injected object or isn't injected in other application services like AuthenticationService.
My question: How can I inject my beans into the application context?
My test:
package ch.swaechter.testapp;
import ch.swaechter.testapp.utils.settings.Settings;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.test.context.junit4.SpringRunner;
#TestConfiguration
#RunWith(SpringRunner.class)
#SpringBootTest(classes = {MyApplication.class})
public class MyApplicationTests {
#MockBean
private Settings settings;
#Before
public void before() {
Mockito.when(settings.getApplicationSecret()).thenReturn("Application Secret");
}
#Test
public void contextLoads() {
String applicationsecret = settings.getApplicationSecret();
System.out.println("Application secret: " + applicationsecret);
}
}
And bellow a service that should use the mocked class, but doesn't receive this mocked class:
package ch.swaechter.testapp;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
#Service
public class AuthenticationServiceImpl implements AuthenticationService {
private final Settings settings;
#Autowired
public AuthenticationServiceImpl(Settings settings) {
this.settings = settings;
}
#Override
public boolean loginUser(String token) {
// Use the application secret to check the token signature
// But here settings.getApplicationSecret() will return null (Instead of Application Secret as specified in the mock)!
return false;
}
}
Looks like you are using Settings object before you specify its mocked behavior.
You have to run
Mockito.when(settings.getApplicationSecret()).thenReturn("Application Secret");
during configuration setup. For preventing that you can create special configuration class for test only.
#RunWith(SpringRunner.class)
#SpringBootTest(classes = {MyApplication.class, MyApplicationTest.TestConfig.class})
public class MyApplicationTest {
private static final String SECRET = "Application Secret";
#TestConfiguration
public static class TestConfig {
#Bean
#Primary
public Settings settingsBean(){
Settings settings = Mockito.mock(Settings.class);
Mockito.when(settings.getApplicationSecret()).thenReturn(SECRET);
Mockito.doReturn(SECRET).when(settings).getApplicationSecret();
return settings;
}
}
.....
}
Also I would recommend you to use next notation for mocking:
Mockito.doReturn(SECRET).when(settings).getApplicationSecret();
It will not run settings::getApplicationSecret
When you annotate a field with #MockBean, spring will create a mock of the annotated class and use it to autowire all beans of the application context.
You must not create the mock yourself with
Settings settings = Mockito.mock(Settings.class);
this would create a second mock, leading to the described problem.
Solution :
#MockBean
private Settings settings;
#Before
public void before() {
Mockito.when(settings.getApplicationSecret()).thenReturn("Application Secret");
}
#Test
public void contextLoads() {
String applicationsecret = settings.getApplicationSecret();
System.out.println("Application secret: " + applicationsecret);
}

SpringBoot unit test configuration

I created a spring-boot 1.4.0 application and I would like to internationlize it using yaml file.
I created a class for loading the configuration from the yaml file like it is explained in the documentation here http://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-external-config.html#boot-features-external-config-typesafe-configuration-properties.
I would like to create a test to check that my class has correctly loaded the properties from the yaml file.
If we keep the exemple from the documentation how to create a unit test that will load a yaml file (with a different name that application.yml) and check that the method getUsername() will return the value from the yaml file ?
Here is the code I have but still can't load the username :
#Component
#ConfigurationProperties(locations = "classpath:mylocalizedprops.yml", prefix="connection")
public class ConnectionProperties {
private String username;
// ... getters and setters
}
and the test class
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest(classes = Application.class)
public class InternationalizationTest {
#Autowired
private ConnectionProperties connectionProperties;
public void propsShouldBeNotNull() {
assertNotNull(connectionProperties);
}
public void userNameShouldBeCorrect() {
assertEquals(connectionProperties.getUsername(), expectedUserName);
}
}
I have failed the userNameShouldBeCorrect test. The file mylocalizedprops.yml is located in the src/main/resources folder of a Maven structured application.
I would consider this an integration test, not a unit-test because you are testing the interaction between various components. Regardless, here is how I would do it.
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = YourApplication.class)
public class InternationalizationTests() {
#Autowired
ConnectionProperties connectionProperties;
#Test
public void testCorrectTranslationLoaded() {
Assert.assertEquals("english-username", connectionProperties.getUsername());
}
}
You can also create a test configuration if you would like to, which you can specify which translation to load. You would then need different classes to test different configurations. See the docs: http://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-testing.html
Unit test can be done easily with Jmockit
import org.junit.jupiter.api.Test;
import org.springframework.boot.SpringApplication;
import org.springframework.context.ConfigurableApplicationContext;
import mockit.Mock;
import mockit.MockUp;
import mockit.Mocked;
import mockit.Verifications;
class RuleApiApplicationTest {
#Mocked
private ConfigurableApplicationContext mockedContext;
#Test
void testApplicationRun() {
new MockUp<SpringApplication>() {
#Mock
public ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
return mockedContext;
}
};
RuleApiApplication.main(new String[]{});
new Verifications() {{
SpringApplication.run(RuleApiApplication.class, new String[]{});
}};
}
}

Categories