I am writing JUnit test cases for my spring application. I use codepro tool in eclipse for generate test cases. when I run this test cases than it is run on JVM not on Tomcat server. so I want to know how it could be run on server? and which is best practice to run test cases on JVM or tomcat? and why? so please suggest me. code is as follow.
import java.io.InputStream;
import java.util.Properties;
import javax.servlet.http.HttpSession;
import junit.framework.TestCase;
import junit.framework.TestSuite;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpSession;
import org.springframework.stereotype.Controller;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.web.bind.annotation.RequestMapping;
import com.zodiacapi.framework.business.ZodiacMobileBusinessTx;
import com.zodiacapi.framework.controller.ZodiacMobileAPIController;
import com.zodiacapi.framework.delegate.SendNotificationDelegate;
import com.zodiacapi.framework.dto.ReturnAPIMessageDTO;
import com.zodiacapi.framework.dto.UserDTO;
import com.zodiacweb.framework.cache.CacheService;
import com.zodiacweb.framework.cache.EhCacheServiceImpl;
import com.zodiacweb.framework.exception.ZodiacWebException;
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration({ "classpath:applicationContext.xml" })
public class ZodiacMobileAPIControllerTest extends TestCase {
private static final Logger logger = LoggerFactory.getLogger(ZodiacMobileAPIControllerTest.class);
#Autowired
private ZodiacMobileBusinessTx zodiabMobileBusinessTx;
public ZodiacMobileBusinessTx getZodiabMobileBusinessTx() {
return zodiabMobileBusinessTx;
}
#Test
public void testMobileLogin_1()
throws Exception {
ReturnAPIMessageDTO entities = new ReturnAPIMessageDTO();
Properties prop = new Properties();
InputStream in = getClass().getResourceAsStream("login.properties");
prop.load(in);
try{
UserDTO result = zodiabMobileBusinessTx.login(prop.getProperty("username"), prop.getProperty("password"), prop.getProperty("apikey"), prop.getProperty("deviceid"), prop.getProperty("deviceModel"));
System.out.println("result of test"+result);
} catch (ZodiacWebException e) {
logger.error("Internal Server Error fetching user info", e);
entities.setStatus("false");
entities.setMessage(e.getMessage());
entities.setVersion("");
} catch (Throwable t) {
entities.setStatus("false");
entities.setMessage(t.getMessage());
entities.setVersion("");
}
}
}
For a unit test you would usually execute it within the JVM. You would probably only execute Integration/Functional tests on an application running in a server.
The choices you have for testing a Spring Controller(That I am familiar with) are:
Test the controller as a regular POJO outside of the container and server
for example : MyController controller = new MyController())
Test the controller using Spring Test MVC. This will actually start up Spring during your tests.(I prefer this option) see Unit Test Spring Controllers for some examples.
If you want to test your application in a real tomcat instance you can use
Arquillian together with The Arquillian Spring Extension. This last option is definitely the most complex in terms of learning curve. But it's nice to be aware of.(Haven't successfully used it with a Spring Application myself)
Don't worry about using Arquillian for now ... it takes some time to learn.
See my code below for a working example of testing a spring controller. I noticed from your code sample that you did not have all the correct annotations and the initialization method.
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = App.class)
#TestPropertySource(locations = "classpath:test.properties")
#WebAppConfiguration
public class AdminUserControllerUnitTest {
MockMvc mvc;
#Autowired
WebApplicationContext webApplicationContext;
#Before
public void initialize(){
mvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
}
#Test
public void testListUsers() throws Exception {
Account account = new Account();
account.setId(1l);
mvc.perform(
get("/admin/user")
.sessionAttr("account",account)
);
.andExpect(MockMvcResultMatchers.model().attribute("users",hasSize(4)));
}
Related
When I try using the below code to test the controllers no errors happen, it just says terminated with no logged messages or anything.
import static org.junit.Assert.assertEquals;
import java.util.Arrays;
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.test.context.junit4.SpringRunner;
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 ....controllers.UserController;
import ....data.response.UserResponse;
import ....models.user.User;
#RunWith(SpringRunner.class)
#WebMvcTest(UserController.class)
public class UserWebMvc {
#Autowired
private MockMvc mvc;
#Test
public void givenEmployees_whenGetEmployees_thenReturnJsonArray() throws Exception {
User alex = new User();
List<UserResponse> allUsers = Arrays.asList(new UserResponse(alex.getId(), alex.getInfo()));
RequestBuilder request = MockMvcRequestBuilders.get("/hello");
MvcResult result = mvc.perform(request).andReturn();
assertEquals(result.getResponse().getContentAsString(), "hello");
// userService.createUser(
// new UserRequest(alex.getInfo().getName(), alex.getInfo().getEmail(), alex.getInfo().getPassword()));
//
}
}
However my test to check that junit is working runs fine, so I'm thinking its something to do with SpringRunner or WebMvc
#SpringBootTest
class BackendApplicationTests {
#Test
void contextLoads() {
assertTrue(false);
}
}
Isn't assertTrue(false) always going to be false? You are basically checking whether false equals true, which will always fail.
Also, from the Spring documentation regarding #WebMvcTest:
Using this annotation will disable full auto-configuration and instead apply only configuration relevant to MVC tests (i.e. #Controller, #ControllerAdvice, #JsonComponent, Converter/GenericConverter, Filter, WebMvcConfigurer and HandlerMethodArgumentResolver beans but not #Component, #Service or #Repository beans).
Using this annotation, it will not scan any beans from the service layer. So if you have a UserService which is injected into the UserController, it will not be found by Spring.
When I run tests like this, I would do something like:
class UserControllerTest {
#InjectMocks
private UserController userController;
private MockMvc mockMvc;
#BeforeEach
public void setUp() {
MockitoAnnotations.openMocks(this);
mockMvc =
MockMvcBuilders.standaloneSetup(userController).build();
}
#Test
public void givenEmployees_whenGetEmployees_thenReturnJsonArray()
throws Exception {
User alex = new User();
List<UserResponse> allUsers = Arrays.asList(new
UserResponse(alex.getId(), alex.getInfo()));
RequestBuilder request = MockMvcRequestBuilders.get("/hello");
MvcResult result = mvc.perform(request).andReturn();
assertEquals(result.getResponse().getContentAsString(),
"hello");
}
}
I was using the wrong import. Changing to this fixed it
import static org.junit.Assert.assertTrue;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
Our project is built with spring boot. Within this project, we've built lot of REST API implementations using the java language, with REST APIs being invocable at URI end-points. Many of these REST API implementations, interact with RDBMS database at back-end (we're using postgresql). The response of all our REST APIs is JSON.
I'm writing API tests that test these REST APIs, using java within the test case, and using JUnit library to write these tests.
I'm writing two kinds of these API tests, as follows,
1) For the REST APIs, that only do read from the database, the test case implementation is simple. I use an HTTP client library from java within my test case, and issue a GET or POST request. I then do 'assert' on the returned JSON response within the test case.
2) For the REST APIs, that do one of create, update or delete on the database, I'm facing challenges to roll back from the database create, update or delete changes that my test case does. In fact, at this moment, I don't know how to roll back create, update or delete changes that my test case does, just before my test case exits. Can anyone please give pointers how to solve this?
Below is also mentioned present source code of one of my test case,
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.io.IOException;
import java.io.InputStream;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.entity.ContentType;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.json.JSONArray;
import org.json.JSONObject;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
public class ApplnCommonRestControllerTest {
private static String API_URI_2 = "http://localhost:8081/appln-restapis/rest/public/common/getalluniversities";
private static CloseableHttpClient fHttpClient;
#Before
public void setUp() throws Exception {
fHttpClient = HttpClients.createDefault();
}
#After
public void tearDown() throws Exception {
fHttpClient.close();
}
#Test
public void getAllUniversitiesResponseBodyTest() throws ClientProtocolException, IOException {
HttpGet httpGet = new HttpGet(API_URI_2);
HttpResponse httpResponse = fHttpClient.execute(httpGet);
InputStream inpStream = (httpResponse.getEntity()).getContent();
String respBodyJsonString = Utilities.inputStreamToString(inpStream);
JSONObject jsonResponeObj = new JSONObject(respBodyJsonString);
JSONArray dataJsonArray = jsonResponeObj.getJSONArray("data");
assertTrue(dataJsonArray.length() >= 2); // test that array contains at least two elements
JSONObject arrElem = (JSONObject)dataJsonArray.get(0); // test first element of array
int univId = arrElem.getInt("id");
String univName = arrElem.getString("universityname");
assertTrue(univId == 1 && univName.length() > 0);
arrElem = (JSONObject)dataJsonArray.get(dataJsonArray.length() - 1);
// test last element of array
univId = arrElem.getInt("id");
univName = arrElem.getString("universityname");
assertTrue(univId > 1 && univName.length() > 0);
}
}
The above sample test case code from our project, does an HTTP GET database read only call. I'm looking for a database roll back solution for the test case, in case the REST API invoked by the test case did a database create/update/delete (i.e, just before or after the test case method exited, the database create/update/delete changes should roll back).
Generally I tend to mark all my #Test methods as #Transnational. But as your test case is completely detached from application context of course this will not help. spring has great integration test library for web layer MockMvc. Using this library your test will look like:
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import org.springframework.transaction.annotation.Transactional;
import static org.hamcrest.Matchers.*;
#RunWith(SpringRunner.class)
#SpringBootTest
#AutoConfigureMockMvc
public class ApplnCommonRestControllerTest {
#Autowired
MockMvc mockMvc;
#Test
#Transactional
public void getAllUniversitiesResponseBodyTest() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("/appln-restapis/rest/public/common/getalluniversities"))
.andDo(MockMvcResultHandlers.print())
.andExpect(MockMvcResultMatchers.jsonPath("$.data")
.value(iterableWithSize(2)))
.andExpect(MockMvcResultMatchers.jsonPath("$.data[0].id", is(1)))
.andExpect(MockMvcResultMatchers.jsonPath("$.data[0].universityname", not(emptyArray())))
.andExpect(MockMvcResultMatchers.jsonPath("$.data[1].id", greaterThan(1)))
.andExpect(MockMvcResultMatchers.jsonPath("$.data[1].universityname", not(emptyArray())));
}
}
And also your test will rollback as this is not really external http call and #Transactional do work in this scenario.
I am receiving the following NullPointerException when trying to execute a Cucumber StepDefs file that passes a JSON from a .feature file to a REST endpoint on localhost;
I have tried to instantiate the ResultActions in every other way, receiving the same error.
The Controller works linked to the test is fine, and is pointing to the correct REST endpoint.
The issue is with the result in personStepDefs
I don't think I'm missing a parameter for ResultActions result as I've built my RequestBuilder
java.lang.NullPointerException at com.///.v2.PersonStepDefs.i\_add\_a\_new\_Person\_using\_POST\_at\_with\_JSON([PersonStepDefs.java:49](https://PersonStepDefs.java:49)) at
✽.I add a new Person using POST at "[http://localhost:8080/services/person/add](http://localhost:8080/services/person/add)" with JSON:(file:///C:/path/to/src/test/resources/Person.feature:6)
PersonStepDefs.java
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import org.junit.BeforeClass;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultActions;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.context.WebApplicationContext;
import io.cucumber.java.en.Then;
import io.cucumber.java.en.When;
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#Transactional
/**
* Person Step Definition class to execute Scenario contained in Person.feature
* #author Lewis Jones
*
*/
public class PersonStepDefs {
#Autowired
private volatile WebApplicationContext wac;
#Autowired
private volatile MockMvc mockMvc;
private ResultActions result;
/**
* Runs the application server before every scenario.
*/
#BeforeClass
public void setup() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
}
#When("I add a new Person using POST at {string} with JSON:")
public void i_add_a_new_Person_using_POST_at_with_JSON(String request, String json) throws Exception {
result = mockMvc.perform(post(request).contentType(MediaType.APPLICATION_JSON)
.content(json.getBytes()));
}
#Then("the response code should be {int}")
public void the_response_code_should_be(Integer responseCode) throws Exception {
result.andExpect(status().is(responseCode));
}
}
RunMvcTest.java
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.web.WebAppConfiguration;
import io.cucumber.junit.Cucumber;
import io.cucumber.junit.CucumberOptions;
#RunWith(Cucumber.class)
#CucumberOptions(
plugin = {"pretty","html:build/cucumber-html-report"},
features = "src/test/resources", strict = true)
#WebAppConfiguration
#ContextConfiguration(classes = V2Application.class)
/**
* A class to run the Cucumber .feature files located in 'features'
* #author Lewis Jones
*
*/
public class RunMvcTest {
}
Person.feature
Feature: Person CRUD
As a User, I want to add a Person
#repo
Scenario: Person.Repo.Add
When I add a new Person using POST at "http://localhost:8080/services/person/add" with JSON:
"""
{"firstName":"Lewis","lastName":"Jones","addressId":"1", "dob":"1999-07-11"}
"""
Then the response code should be 200
A null pointer exception happens when you try to de-reference a variable or field that is null. So if you try to call perform on mockMvc while mockMvc is null you will get a null pointer exception.
If you read the stack trace carefully you can see that this it what it tries to tell you.
result = mockMvc.perform(....);
So how can mockMcv be null? You initialize it in setup method right? That means setup isn't called. You can confirm this by putting a break point in the method and debugging your test.
import org.junit.BeforeClass;
....
/**
* Runs the application server before every scenario.
*/
#BeforeClass
public void setup() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
}
And setup doesn't get called because BeforeClass is a JUnit annotation. Cucumber uses io.cucumber.java.Before.
I have a spring-boot application where my #SpringBootApplication starter class looks like a standard one. So I created many tests for all my functionalities and send the summary to sonarqube to see my coverage.
For my starter class Sonarqube tells me that I just have 60% coverage. So the average coverage is not good as expected.
My Test class is just the default one.
#RunWith(SpringRunner.class)
#SpringBootTest(classes = ElectronicGiftcardServiceApplication.class)
public class ElectronicGiftcardServiceApplicationTests {
#Test
public void contextLoads() {
}
}
So how can I test my main class in the starter class of my application?
All these answers seem overkill.
You don't add tests to make a metric tool happy.
Loading a Spring context of the application takes time. Don't add it in each developer build just to win about 0.1% of coverage in your application.
Here you don't cover only 1 statement from 1 public method. It represents nothing in terms of coverage in an application where thousands of statements are generally written.
First workaround : make your Spring Boot application class with no bean declared inside. If you have them, move them in a configuration class (for make them still cover by unit test). And then ignore your Spring Boot application class in the test coverage configuration.
Second workaround : if you really need to to cover the main() invocation (for organizational reasons for example), create a test for it but an integration test (executed by an continuous integration tool and not in each developer build) and document clearly the test class purpose :
import org.junit.Test;
// Test class added ONLY to cover main() invocation not covered by application tests.
public class MyApplicationIT {
#Test
public void main() {
MyApplication.main(new String[] {});
}
}
You can do something like this
#Test
public void applicationContextLoaded() {
}
#Test
public void applicationContextTest() {
mainApp.main(new String[] {});
}
I solved in a different way here. Since this method is there only as a bridge to Spring's run, I annotated the method with #lombok.Generated and now sonar ignores it when calculating the test coverage.
Other #Generated annotations, like javax.annotation.processing.Generated or javax.annotation.Generated might also work but I can't test now because my issue ticket was closed.
package com.stackoverflow;
import lombok.Generated;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
#SpringBootApplication
public class Application {
#Generated
public static void main(String... args) {
SpringApplication.run(Application.class, args);
}
}
I had the same goal (having a test that runs the main() method) and I noticed that simply adding a test method like #fg78nc said will in fact "start" the application twice : once by spring boot test framework, once via the explicit invocation of mainApp.main(new String[] {}), which I don't find elegant.
I ended up writing two test classes : one with #SpringBootTest annotation and the empty test method applicationContextLoaded(), another one without #SpringBootTest (only RunWith(SpringRunner.class)) that calls the main method.
SpringBootApplicationTest
package example;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.boot.test.context.SpringBootTest;
#RunWith(SpringRunner.class)
#SpringBootTest
public class SpringBootApplicationTest {
#Test
public void contextLoads() {
}
}
ApplicationStartTest
package example;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.junit4.SpringRunner;
#RunWith(SpringRunner.class)
public class ApplicationStartTest {
#Test
public void applicationStarts() {
ExampleApplication.main(new String[] {});
}
}
Overall, the application is still started two times, but because there is now two test classes. Of course, with only these two tests methods, it seems overkill, but usually more tests will be added to the class SpringBootApplicationTest taking advantage of #SpringBootTest setup.
In addition to the answers above, here is a unit test of a SpringBoot application's main method for if you are using JUnit 5 and Mockito 3.4+:
try (MockedStatic<SpringApplication> mocked = mockStatic(SpringApplication.class)) {
mocked.when(() -> { SpringApplication.run(ElectronicGiftCardServiceApplication.class,
new String[] { "foo", "bar" }); })
.thenReturn(Mockito.mock(ConfigurableApplicationContext.class));
ElectronicGiftCardServiceApplication.main(new String[] { "foo", "bar" });
mocked.verify(() -> { SpringApplication.run(ElectronicGiftCardServiceApplication.class,
new String[] { "foo", "bar" }); });
}
It verifies that the static method run() on the SpringApplication class is called with the expected String array when we call ElectronicGiftCardServiceApplication.main().
Same idea as awgtek and Ramji Sridaran, but their solutions are for JUnit 4.
You can Mock SpringApplication since that is a dependency of the method under test. See how here.
I.e.
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.springframework.boot.SpringApplication;
import static org.powermock.api.mockito.PowerMockito.mockStatic;
import static org.powermock.api.mockito.PowerMockito.verifyStatic;
#RunWith(PowerMockRunner.class)
public class ElectronicGiftcardServiceApplicationTest {
#Test
#PrepareForTest(SpringApplication.class)
public void main() {
mockStatic(SpringApplication.class);
ElectronicGiftcardServiceApplication.main(new String[]{"Hello", "World"});
verifyStatic(SpringApplication.class);
SpringApplication.run(ElectronicGiftcardServiceApplication.class, new String[]{"Hello", "World"});
}
}
Using junit
import org.junit.jupiter.api.Test;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.springframework.boot.SpringApplication;
import static org.assertj.core.api.Assertions.*;
class WebsiteApplicationTests {
#Test
void testApplication() {
MockedStatic<SpringApplication> utilities = Mockito.mockStatic(SpringApplication.class);
utilities.when((MockedStatic.Verification) SpringApplication.run(WebsiteApplication.class, new String[]{})).thenReturn(null);
WebsiteApplication.main(new String[]{});
assertThat(SpringApplication.run(WebsiteApplication.class, new String[]{})).isEqualTo(null);
}
}
Add these dependencies in pom.xml
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>
<version>${mockito.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>3.8.0</version>
<scope>test</scope>
</dependency>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<mainClass>your.awesome.package.Application</mainClass>
</configuration>
</plugin>
If you aim for 100% coverage, one thing you can do is simply not having a main method at all. You still require a class annotated with #SpringBootApplication but it can be empty.
Be warned though as it has its drawbacks and other tools that rely on main can break.
This simple mock test for SpringApplication does not invoke any methods but just tests the starter app. [uses PowerMockRunner.class]
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PowerMockIgnore;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.springframework.boot.SpringApplication;
#RunWith(PowerMockRunner.class)
#PowerMockIgnore({"com.sun.org.apache.xerces.*", "javax.xml.*", "org.xml.*", "javax.management.*"})
public class JobsAppStarterTest {
#Test
#PrepareForTest(SpringApplication.class)
public void testSpringStartUp() {
PowerMockito.mockStatic(SpringApplication.class);
SpringApplication.run(JobsAppStarter.class, new String[] {"args"});
JobsAppStarter.main(new String[] {"args"});
}
}
If the idea is to exclude the SpringApplication class from sonar scan (which is the recommended way of doing it), you can exclude it with the following configuration in the build.gradle
plugins {
id 'org.sonarqube' version '3.4.0.2513'
}
sonarqube {
properties {
property "sonar.exclusions", "**/*Application.java"
}
}
Even though this question has been answered extensively I had a use case that is not covered here that is perhaps interesting to share. I am validating some properties at startup and I wanted to assert that the application would fail to start if these properties were configured wrong. In JUnit4 I could have done something like this:
#ActiveProfiles("incorrect")
#SpringBoot
public class NetworkProbeApplicationTest {
#Test(expected=ConfigurationPropertiesBindException.class)
public void contextShouldNotLoadWhenPropertiesIncorrect() {
}
}
But in JUnit5 you can no longer add the "expected" value to your #Test annotation and you have to do it differently. And since I wanted to start the application with an incorrect set of properties I needed to pass in which profile to use as a main() argument. I could not really find this documented anywhere, but passing in arguments through the main() method requires you to prefix your arguments with a double hyphen and separate the key and value with an equals sign. A complete test would look like this:
import org.junit.jupiter.api.Test;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.context.properties.ConfigurationPropertiesBindException;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class NetworkProbeApplicationTest {
#Test
public void contextShouldNotLoadWhenPropertiesIncorrect() {
Exception exception = assertThrows(ConfigurationPropertiesBindException.class, () -> {
SpringApplication.run(NetworkProbeApplication.class, "--spring.profiles.active=incorrect");
});
String expectedMessage = "Error creating bean with name 'dnsConfiguration': Could not bind properties to 'DnsConfiguration' : prefix=dns";
assertTrue(exception.getMessage().contains(expectedMessage));
}
}
We are currently using Spring Boot to connect to a mocked local instance of Amazon SQS. The application itself is working when run, but we would like to try and test the SQS Config class, if possible and if it makes sense.
Here is the configuration class. All properties are pulled from the typical application.properties file when the Spring application itself is run.
import com.amazonaws.services.sqs.AmazonSQSAsync;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.aws.core.env.ResourceIdResolver;
import org.springframework.cloud.aws.messaging.core.QueueMessagingTemplate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
#Configuration
public class AWSSQSConfig {
#Value("${aws.sqs.endpoint}")
private String AWSSqsEndpoint;
// Producer QueueMessageTemplate
#Bean
public QueueMessagingTemplate queueMessagingTemplate(AmazonSQSAsync amazonSqs, ResourceIdResolver resourceIdResolver) {
if (!AWSSqsEndpoint.isEmpty())
amazonSqs.setEndpoint(AWSSqsEndpoint);
return new QueueMessagingTemplate(amazonSqs, resourceIdResolver);
}
}
Here is the test class. We are attempting to pass the configuration in via TestPropertySource, but they don't actually seem to get to the AWSSQSConfig class. AWSSqsEndpoint inside the instance of the class is always NULL.
import com.amazonaws.services.sqs.AmazonSQSAsync;
import com.lonewolf.formsbuilder.config.AWSSQSConfig;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.aws.core.env.ResourceIdResolver;
import org.springframework.cloud.aws.messaging.core.QueueMessagingTemplate;
import org.springframework.test.context.TestPropertySource;
import static org.junit.Assert.assertNotNull;
#RunWith(MockitoJUnitRunner.class)
#SpringBootTest
#TestPropertySource(properties = {
"cloud.aws.region.static=us-east-1",
"cloud.aws.credentials.accessKey=zzzzz",
"cloud.aws.credentials.secretKey=zzzzzz",
"aws.sqs.endpoint = http://localhost:9324",
"aws.sqs.requestQueue = CreateSchemaRequest",
"aws.sqs.responseQueue = CreateSchemaResponse"
})
public class AWSSQSConfigTests {
#Mock
private AmazonSQSAsync amazonSqs;
#Mock
private ResourceIdResolver resourceIdResolver;
#Test
public void contextLoads() {
AWSSQSConfig config = new AWSSQSConfig();
QueueMessagingTemplate queueMessagingTemplate = config.queueMessagingTemplate(amazonSqs, resourceIdResolver);
assertNotNull("The response body must not be null", queueMessagingTemplate);
}
}
Is this a chicken and the egg situation, where the spring framework actually needs to run first to inject those config values? Do we need an integration test here instead?
EDIT with working solution...
Using the accepted answer, here is my working test! I was able to remove my dependency of the Spring framework.
#RunWith(MockitoJUnitRunner.class)
public class AWSSQSConfigTests {
#Mock
private AmazonSQSAsync amazonSqs;
#Mock
private ResourceIdResolver resourceIdResolver;
#InjectMocks
private AWSSQSConfig config;
#Before
public void setup() {
ReflectionTestUtils.setField(config, "AWSSqsEndpoint", "http://fake");
}
#Test
public void contextLoads() {
QueueMessagingTemplate queueMessagingTemplate = config.queueMessagingTemplate(amazonSqs, resourceIdResolver);
assertNotNull("The response body must not be null", queueMessagingTemplate);
}
}
Have you tried injecting mock to your class (or autowire it), and then setting that field it using ReflectionTestUtils? This is a nice test utils class that Spring provides that allows you to do something like what you want without doing code modifications.
I mean something like this:
#InjectMocks
private AWSSQSConfig awssqsConfig;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
ReflectionTestUtils.setField(awssqsConfig, "AWSSqsEndpoint", "putYourEndpointHere");
}