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");
}
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;
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);
}
I know I should not be testing void methods like this, but I am just testing Mockito.doNothing() as of now with a simple example.
My Service class:
#Service
public class Service{
#Autowired
private Consumer<String, String> kafkaConsumer;
public void clearSubscribtions(){
kafkaConsumer.unsubscribe();
}
}
My Test class:
#MockBean
private Consumer<String, String> kafkaConsumer;
#Test
public void testClearSubscriptions() {
Service service = new Service();
Mockito.doNothing().when(kafkaConsumer).unsubscribe();
service.clearSubscriptions();
}
The test keeps failing with a null pointer exception. When I debugged it, it goes into the clearSubscription method of the service class, and there on the line of kafkaConsumer.unsubscribe(), kafkaConsumer is null. But I mocked the consumer, why is it throwing null pointer exception and I should be skipping over that method, right?
Edit:
All the declarations of the class:
#Autowired
private Consumer<String, String> kafkaConsumer;
#Autowired
private Service2 service2;
private final Object lock = new Object();
private static Logger logger = LoggerFactory.getLogger(Service.class);
private HashMap<String, String> subscribedTopics = new HashMap<>();
Figured out what was wrong, I needed to auto wire the service
You are instantiating a new service Service service = new Service(); but from what I can see you are never injecting the mock bean into the new service.
Here is a sample of what I think you could do if you are using mockito only and dont need to instantiate a spring container (used a single class for ease of example dont do this in actual code):
package com.sbp;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
#RunWith(MockitoJUnitRunner.class) // run with mockitos runner so annotations are processed
public class MyServiceTest {
public interface Consumer<T, R> {
public void unsubscribe();
}
#Service
public class KafkaConsumer implements Consumer<String, String> {
#Override
public void unsubscribe() {
}
}
#Service
public class MyService {
#Autowired
private Consumer<String, String> kafkaConsumer;
public void clearSubscriptions() {
kafkaConsumer.unsubscribe();
}
}
#Mock // tell mockito that this is a mock class - it will instantiate for you
private Consumer<String, String> kafkaConsumer;
#InjectMocks // tell mockito to inject the above mock into the class under test
private MyService service = new MyService();
#Test
public void testClearSubscriptions() {
service.clearSubscriptions();
Mockito.verify(kafkaConsumer, Mockito.times(1)).unsubscribe();
}
}
If you need an example via Spring using MockBean or without and dependencies, let me know and I can post.
UPDATED: adding sample using spring junit runner and using spring boot's mockbean annotation
package com.sbp;
import com.sbp.MyServiceTest.TestContext.MyService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Service;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
#RunWith(SpringJUnit4ClassRunner.class) // run with spring
#SpringBootTest(classes = MyServiceTest.TestContext.class) // make it a spring boot test so #MockBean annotation is processed, provide a dummy test context class
public class MyServiceTest {
public interface Consumer<T, R> {
public void unsubscribe();
}
#Configuration
public static class TestContext {
#Service
public class KafkaConsumer implements Consumer<String, String> {
#Override
public void unsubscribe() {
}
}
#Service
public class MyService {
#Autowired
private Consumer<String, String> kafkaConsumer;
public void clearSubscriptions() {
kafkaConsumer.unsubscribe();
}
}
}
#MockBean // this will create a mockito bean and put it in the application context in place of the Kafka consumer bean defined in the TestContext class
private Consumer<String, String> kafkaConsumer;
#Autowired // inject the bean from the application context that is wired with the mock bean
private MyService myService;
#Test
public void testClearSubscriptions() {
myService.clearSubscriptions();
Mockito.verify(kafkaConsumer, Mockito.times(1)).unsubscribe();
}
}
I try to use Springs own Dependency Injection in a Junit test case:
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import org.binarisinformatik.api.AppConfig;
import org.binarisinformatik.satzrechner.SatzRechner;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes=AppConfig.class)
//#SpringApplicationConfiguration(classes = {AppConfig.class})
public class SatzRechnerTest {
#Autowired
private SatzRechner satzRechner; //SUT
#Before
public void setUp() {
// AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SatzRechnerTest.class);
//satzRechner=context.getBean(SatzRechner.class);
}
#Test
public void addiere_satz_4komma6_zu_zahlwert_10() {
assertThat("Addition von \"4,6\" ergibt nicht 10!",
satzRechner.summe("4,6"), is(equalTo(10)));
}
Im testing a class names SatzRechner in which Spring should also autowire some variables. Here is my Class under test:
#Component
public class SatzRechner {
#Autowired //#Inject
private Rechner taschenRechner;
#Autowired
private Zahlenfabrik zahlenfabrik;
public Integer summe(String zeichenSatz) {
return taschenRechner.summe(zahlenfabrik.erzeugeZahlen(zeichenSatz));
}
}
And AppConfig.class which is using as Configurationfile looks like that:
#Configuration
#ComponentScan(value={"org.binarisinformatik"})
public class AppConfig {
}
What is here the problem?
If you want to use a Spring configuration class, this one must have beans definitions. Please find an example below :
Test class:
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import org.binarisinformatik.api.AppConfig;
import org.binarisinformatik.satzrechner.SatzRechner;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes=AppConfig.class)
public class SatzRechnerTest {
#Autowired
private SatzRechner satzRechner;
#Test
public void addiere_satz_4komma6_zu_zahlwert_10() {
assertThat("Addition von \"4,6\" ergibt nicht 10!",
satzRechner.summe("4,6"), is(equalTo(10)));
}
}
Configuration class :
You have to declare #Bean annotated methods. These beans are managed by Spring container.
#Configuration
public class AppConfig {
// Beans present here will be injected into the SatzRechnerTest class.
#Bean
public SatzRechner satzRechner() {
return new SatzRechner();
}
#Bean
public Rechner taschenRechner() {
return new TaschenRechner();
}
#Bean
public Zahlenfabrik zahlenfabrik() {
return new Zahlenfabrik();
}
}
Note : I let you properly handle returned types here and beans parameters (if present in your context).
There are two things you have to ensure before you run the test case successfully:
1) Classes SatzRechner, Rechner & Zahlenfabrik should be under "org.binarisinformatik" package
2) Classes Rechner & Zahlenfabrik should also be annotated with #Component as SatzRechner.
I'm struggling with testing access control on URLs protected by Spring Security.
The configuration looks like this:
http
.authorizeRequests()
.antMatchers("/api/user/**", "/user").authenticated()
.antMatchers("/api/admin/**", "/templates/admin/**", "/admin/**").hasAuthority("ADMIN")
.anyRequest().permitAll();
And the test class looks like this:
package com.kubukoz.myapp;
import com.kubukoz.myapp.config.WebSecurityConfig;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.transaction.TransactionConfiguration;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import javax.transaction.Transactional;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = {MyApplication.class, WebSecurityConfig.class})
#WebAppConfiguration
#TransactionConfiguration(defaultRollback = true)
#Transactional(rollbackOn = Exception.class)
public class MyApplicationTests {
#Autowired
private WebApplicationContext context;
private MockMvc mockMvc;
#Autowired
private FilterChainProxy filterChainProxy;
#Before
public void setUp() {
mockMvc = MockMvcBuilders.webAppContextSetup(context)
.dispatchOptions(true)
.addFilters(filterChainProxy)
.build();
}
#Test
public void testAnonymous() throws Exception {
mockMvc.perform(get("/api/user/account")).andExpect(status().is3xxRedirection());
}
#Test
public void testUserAccessForAccount() throws Exception{
mockMvc.perform(get("/api/user/account")).andExpect(status().isOk());
}
}
What's the easiest way to make the last two tests pass?
#WithMockUser didn't work.
You should not add the FilterChainProxy directly. Instead, you should apply SecurityMockMvcConfigurers.springSecurity() as indicated by the reference. An example is included below:
mockMvc = MockMvcBuilders
.webAppContextSetup(context)
.apply(springSecurity())
.build();
The result of this is:
the FilterChainProxy is added as a Filter to MockMvc (as you did)
the TestSecurityContextHolderPostProcessor is added
Why is TestSecurityContextHolderPostProcessor necessary? The reason is that we need to communicate the current user from the test method to the MockHttpServletRequest that is created. This is necessary because Spring Security's SecurityContextRepositoryFilter will override any value on SecurityContextHolder to be the value found by the current SecurityContextRepository (i.e. the SecurityContext in HttpSession).
Update
Remember anything that contains role in the method name automatically prefixes "ROLE_" to the string that was passed in.
Based on your comment, the problem is you need to either update your configuration to use hasRole instead of hasAuthority (since your annotation is using roles):
.authorizeRequests()
.antMatchers("/api/user/**", "/user").authenticated()
.antMatchers("/api/admin/**", "/templates/admin/**", "/admin/**").hasRole("ADMIN")
.anyRequest().permitAll();
Alternatively
You in Spring Security 4.0.2+ you can use:
#WithMockUser(authorities="ADMIN")
Okay, figured it out.
mockMvc.perform(get("/api/user/account")
.with(user("user")))
.andExpect(status().isOk());
It works now.