How to correctly stop and start a localstack instance - testcontainers

I am using Localstack to write integration tests. One of the scenarios I want to test is when the AWS service throws error. My test looks like
void saveEventsAsync_snsError() throws Exception{
localSns.stop();
mvc.perform( MockMvcRequestBuilders
.post("/events-async")
.content(validRequest())
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isInternalServerError());
}
However doing do fails one another test with HttpConnection refused.. error. Presumably the container didn't start by the time the test ran.
What are my options here ? Looping with localSns.isRunning() doesn;t seem to help
Here's full test clas for reference
package ....
import org.junit.jupiter.api.*;
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.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Profile;
import org.springframework.http.MediaType;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.testcontainers.containers.localstack.LocalStackContainer;
import org.testcontainers.containers.wait.strategy.DockerHealthcheckWaitStrategy;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import org.testcontainers.utility.DockerImageName;
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.sns.SnsClient;
import software.amazon.awssdk.services.sns.model.CreateTopicRequest;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.testcontainers.containers.localstack.LocalStackContainer.Service.SNS;
#SpringBootTest
#AutoConfigureMockMvc
#ActiveProfiles("test")
#Testcontainers
#TestMethodOrder(MethodOrderer.OrderAnnotation.class)
class EventReportingControllerTest {
#Autowired
MockMvc mvc;
#Container
private static final LocalStackContainer localSns =
new LocalStackContainer(DockerImageName.parse("localstack/localstack:0.11.3"))
.withServices(SNS);
private static SnsClient snsClient;
private static String topicArn ;
#BeforeAll
static void beforeAll() {
localSns.start();
snsClient = SnsClient.builder()
.endpointOverride(localSns.getEndpointOverride(SNS))
.credentialsProvider(
StaticCredentialsProvider.create(
AwsBasicCredentials.create(localSns.getAccessKey(), localSns.getSecretKey())
)
)
.region(Region.of(localSns.getRegion()))
.build();
topicArn = snsClient.createTopic(CreateTopicRequest.builder().name("testTopic").build()).topicArn();
}
#DynamicPropertySource
public static void overrideProps(DynamicPropertyRegistry registry){
registry.add("events.sns.topic.arn", () -> snsClient.createTopic(CreateTopicRequest.builder().name("testTopic").build()).topicArn());
}
#TestConfiguration
public static class SnsClientConfig {
#Bean
#Profile("test")
public SnsClient amazonSNSClient() {
return snsClient;
}
}
#Test
void saveEventsAsync_success() throws Exception{
mvc.perform( MockMvcRequestBuilders
.post("/events-async")
.content(validRequest())
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isAccepted()
);
}
#Test
void saveEventsAsync_invalidRequest() throws Exception{
mvc.perform( MockMvcRequestBuilders
.post("/events-async")
.content(invalidRequest())
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isBadRequest());
}
#Test
void saveEventsAsync_snsError() throws Exception{
localSns.stop();
mvc.perform( MockMvcRequestBuilders
.post("/events-async")
.content(validRequest())
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isInternalServerError());
}
}

When using #Testconainers and #Container for your setup, Testcontainers will manage the lifecycle of the container for you.
Hence, you currently have a mix of self-manging and "managed by Testcontainers".
If you want to take over the lifecycle handling, remove the two annotations and start/stop the container according to your preference.
PS: If you're using a recent Spring Cloud AWS version, overriding the AWS SDK client is way simpler now.

Related

Spring Boot - unit test not detecting the controller test

I have written unit test for the controller class but when I run unit test using "mvn test" or directly from "SpringBootDemo1ApplicationTests" only test under "SpringBootDemo1ApplicationTests" class runs, and it doesn't pick up test from "BookControllerTest" class.
SpringBootDemo1ApplicationTests.java:
package com.sprboot.SpringBootDemo1;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
#SpringBootTest
class SpringBootDemo1ApplicationTests {
#Test
void contextLoads() {
}
}
BookControllerTest.java
package com.sprboot.SpringBootDemo1;
import com.sprboot.SpringBootDemo1.controllers.BookController;
import com.sprboot.SpringBootDemo1.models.Book;
import com.sprboot.SpringBootDemo1.services.BookService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.json.AutoConfigureJsonTesters;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.json.JacksonTester;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.test.web.servlet.MockMvc;
import java.util.Optional;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import static org.mockito.BDDMockito.given;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
#AutoConfigureJsonTesters
#WebMvcTest(BookController.class)
#SpringBootTest
public class BookControllerTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private BookService bookService;
#Autowired
private JacksonTester<Book> jsonBook;
#Test
private void canRetriveBookById() throws Exception {
given(bookService.getBookByID(123)).willReturn(Optional.of(new Book(123, "test book")));
MockHttpServletResponse response = mockMvc.perform(
get("/getBookById/123")
.accept(MediaType.APPLICATION_JSON))
.andReturn().getResponse();
// then
assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value());
assertThat(response.getContentAsString()).isEqualTo(
jsonBook.write(new Book(1, "test book")).getJson()
);
}
private void canRetriveBookByIdV2() throws Exception {
given(bookService.getBookByID(123)).willReturn(Optional.of(new Book(123, "test book")));
MockHttpServletResponse response = mockMvc.perform(
get("/getBookById/123")
.accept(MediaType.APPLICATION_JSON))
.andReturn().getResponse();
// then
assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value());
assertThat(response.getContentAsString()).isEqualTo(
jsonBook.write(new Book(1, "test book")).getJson()
);
}
}
I was expecting test "canRetriveBookById" to run as well but only test "contextLoads" runs, not sure whats wrong here.
It's because your #Test method is private.
From the #Test annotations documentation:
#Test methods must not be private or static and must not return a value.

How to overwrite #WithMockUser set on class in a single test in spring boot

If I have the annotation #WithMockUser on a test class in spring boot, how can I overwrite/null this setting for a single test in which I want to see how the code behaves without set principal?
If you want to see how the code behaves with a different user, you can just put another #WithMockUser directly on the method.
#SpringBootTest
#WithMockUser(username="user", password="password")
public class UserSecurityTest {
#Test
#WithMockUser(username="otherUser", password="password")
public void testMockUserOverride() {
...
}
}
If you want to see how the code behaves with no credentials then you need to clear out the security context at the beginning of your test.
#SpringBootTest
#WithMockUser(username="user", password="password")
public class UserSecurityTest {
#Test
public void testNoAuth() {
SecurityContextHolder.clearContext();
...
}
}
Here is a complete example that includes a test that runs after clearing out the authentication to be sure it is restored to what the class-level mock is set to.
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.test.context.support.WithMockUser;
import java.util.Optional;
import static org.junit.jupiter.api.Assertions.assertEquals;
#SpringBootTest
#WithMockUser(username = "user", password = "password")
#TestMethodOrder(MethodOrderer.class)
public class SecurityTest {
public String getCurrentUser() {
return Optional.ofNullable(SecurityContextHolder.getContext())
.map(SecurityContext::getAuthentication)
.map(Authentication::getPrincipal)
.map(user -> ((User)user).getUsername())
.orElse("noAuth");
}
#Test
#Order(0)
public void testClassLevelMockUser() {
assertEquals("user",getCurrentUser());
}
#Test
#Order(1)
#WithMockUser(username = "otherUser")
public void testOverrideMock() {
assertEquals("otherUser", getCurrentUser());
}
#Test
#Order(2)
public void testNoAuth() {
SecurityContextHolder.clearContext();
assertEquals("noAuth", getCurrentUser());
}
#Test
#Order(3)
public void testClassLevelMockUserNotDestroyedByOtherTest() {
assertEquals("user", getCurrentUser());
}
}

#Scheduled works in Spring Boot application but not in #SpringBootTest

What is the reason that the #Scheduled function runs in Spring Boot Application but not in test environment (#SpringBootTest) ?
I am following the tutorial at https://github.com/spring-guides/gs-scheduling-tasks, the repository's test runs fine, but mine #Scheduled function does not run for once in my test, although it is working fine in my Spring Boot application.
Is it because of the version of Junit (The tutorial is using Junit5 while I am using JUnit 4) ?
My purpose of this test is to check the correctness of the configuration of my scheduler.
Below is the code I replicated with difference in Junit version.
BootApplication.java
package com.itsedo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.support.SpringBootServletInitializer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.scheduling.annotation.EnableScheduling;
// SecurityAutoConfiguration: exclude default spring security boot file.
#SpringBootApplication(exclude = { SecurityAutoConfiguration.class })
#ComponentScan("com.itsedo")
#EnableScheduling
public class BootApplication extends SpringBootServletInitializer {
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(BootApplication.class);
}
public static void main(String[] args) throws Exception {
SpringApplication.run(BootApplication.class, args);
}
}
ScheduledTasks.java
package com.itsedo.scheduler;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
#Component
public class ScheduledTasks {
private static final Logger log = LoggerFactory.getLogger(ScheduledTasks.class);
private static final SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");
#Scheduled(fixedRate = 5000)
public void reportCurrentTime() {
log.info("The time is now {}", dateFormat.format(new Date()));
}
}
File ScheduledTasksTest.java
package com.itsedo.test;
import org.awaitility.Duration;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.SpyBean;
import org.springframework.test.context.junit4.SpringRunner;
import static org.awaitility.Awaitility.await;
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.verify;
import com.itsedo.scheduler.ScheduledTasks;
#RunWith(SpringRunner.class)
#SpringBootTest
public class ScheduledTasksTest {
#SpyBean
ScheduledTasks tasks;
#Test
public void reportCurrentTime() {
await().atMost(Duration.TEN_SECONDS).untilAsserted(() -> {
verify(tasks, atLeast(2)).reportCurrentTime();
});
}
}
Test Output
Running Spring Boot application

Getting 500 error while testing webflux code with wiremock

I have spring webflux app with user controller class with end point "/user and user service class.The user service class making call to external api. I am trying to test the service class using wiremock and junit 5 to mock out external api.. However I am getting below error ->
021-07-30 18:22:52.511 ERROR 16974 --- [o-auto-1-exec-2] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] threw exception
java.net.ConnectException: Connection refused
Code is uploaded at path : https://github.com/neeleshsethi/wiremockdemp/tree/master
It seems it cannot find controller as adding a print statement in controller is not printing anything. Below is the code ->
#Service
public class UserService {
#Autowired
WebClient webClient;
public Mono<User> createuser(User user) {
return webClient.post()
.uri("/usercreate")
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromValue(user))
.retrieve()
.bodyToMono(User.class);
}
}
WireMock Inti class:
package com.example.wiremockdemo;
import com.github.tomakehurst.wiremock.WireMockServer;
import com.github.tomakehurst.wiremock.core.WireMockConfiguration;
import org.springframework.boot.test.util.TestPropertyValues;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.event.ContextClosedEvent;
import java.util.Map;
public class WireMockInit implements ApplicationContextInitializer<ConfigurableApplicationContext> {
#Override
public void initialize(ConfigurableApplicationContext applicationContext) {
WireMockServer wireMockServer = new WireMockServer(new WireMockConfiguration().dynamicPort());
wireMockServer.start();
applicationContext.addApplicationListener( applicationEvent ->
{
if(applicationEvent instanceof ContextClosedEvent)
{
wireMockServer.stop();
}
}
);
applicationContext.getBeanFactory().registerSingleton("wireMockServer", wireMockServer);
TestPropertyValues.of("externalBaseUrl",wireMockServer.baseUrl())
.applyTo(applicationContext);
}
}
test class:
package com.example.wiremockdemo;
import com.example.wiremockdemo.model.User;
import com.github.tomakehurst.wiremock.WireMockServer;
import com.github.tomakehurst.wiremock.client.WireMock;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.http.MediaType;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.web.reactive.function.BodyInserters;
import reactor.core.publisher.Mono;
import java.awt.*;
import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#ContextConfiguration(initializers = {WireMockInit.class})
class WiremockdemoApplicationTests {
#Autowired
WebTestClient webTestClient;
#Autowired
private WireMockServer wireMockServer;
#LocalServerPort
private Integer port;
#Test
void createUsertest() {
System.out.println("Creating stub");
wireMockServer.stubFor(
WireMock.post("/usercreate")
.willReturn(
aResponse()
.withHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE)
.withBodyFile("response.json"))
);
byte[] temp = webTestClient.post()
.uri("http://localhost:" + port + "/user")
// .uri("/user")
.contentType(MediaType.APPLICATION_JSON)
//.accept(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromValue(createUser()))
.exchange()
.expectBody()
.returnResult()
.getResponseBody();
String s = new String(temp);
System.out.println("Response :" +s);
}
public User createUser()
{
return User.builder()
.firstName("neel")
.age(32)
.id(1234)
.build();
}
}
Change this line to:
TestPropertyValues.of(Map.of("externalBaseUrl", wireMockServer.baseUrl()));
This is the correct way to set test properties.

Unable to Mock RestTemplate.exchange

As part of TDD i want to be able to test every portion of my SpringBoot rest application. However i am unable to mock external calls.
Application structure
1. Few rest endpoints which internally call external rest endpoints.
2. All calls to external endpoints are orchestrated through a local http client which utilizes RestTemplate as httpClient.
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = WebEnvironment.MOCK, classes = TestDrivenDevelopmentWithJavaApplication.class)
public class TestDrivenDevelopmentWithJavaApplicationTests {
#Autowired
private WebApplicationContext webApplicationContext;
private MockMvc mockMvc;
#MockBean
private RestTemplate client;
#Before
public void setup() throws Exception {
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
Structure1Root category = new Structure1Root();
Category cat = new Category();
cat.setCategoryName("Test1");
cat.setDescription("Test");
category.setD(cat);
Mockito.when(client.exchange(
ArgumentMatchers.eq("https://services.odata.org/V2/Northwind/Northwind.svc/Products(1)?$format=json"),
ArgumentMatchers.eq(HttpMethod.GET), ArgumentMatchers.eq(null),
ArgumentMatchers.eq(Structure1Root.class)))
.thenReturn(new ResponseEntity<Structure1Root>(category, HttpStatus.OK));
}
#Test
public void testendpoint1() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("/api/endpoint1?token=1").contentType(MediaType.APPLICATION_JSON))
.andExpect(content().string(org.hamcrest.Matchers.containsString("Test1")));
}
}
Even though i have setup the mock on client.exchange(RestTemplate.exchange), i see response returned by client.exchange is null and not the response specified in thenReturn
Controller Code
#RestController
#RequestMapping(path = Endpoint.base)
public class Endpoint {
public static final String base = "/api";
#Autowired
MyHttpClient<Structure2Root> client;
#Autowired
MyHttpClient<Structure1Root> Cclient;
#GetMapping(path = "/endpoint1")
public ResponseEntity<Structure2Root> callEndpt1(#RequestParam String token) {
Response<Structure2Root> resp = client
.execute("https://services.odata.org/V2/Northwind/Northwind.svc/Products(1)?$format=json", Structure2Root.class);
return new ResponseEntity<Structure2Root>(resp.getResponse(), HttpStatus.OK);
}
#GetMapping(path = "/endpoint2")
public ResponseEntity<Structure1Root> callEndpt2(#RequestParam String token) {
Response<Structure1Root> resp = Cclient.execute(
"https://services.odata.org/V2/Northwind/Northwind.svc/Categories(1)?$format=json", Structure1Root.class);
return new ResponseEntity<Structure1Root>(resp.getResponse(),HttpStatus.OK);
}
}
And finally, local http client code
#Service
public class MyHttpClient<O> {
#Autowired
RestTemplate client;
public MyHttpClient() {
// TODO Auto-generated constructor stub
}
public Response<O> execute(String url, Class<O> generic) {
ResponseEntity<O> resp = client.exchange(url, HttpMethod.GET, null, generic);
return new Response<O>(resp.getStatusCode(), resp.getBody());
}
}
this client.execute is what i intend to intercept in the first code block
However never seems to work and always returns a null.
The Git Repo
Regards,
Veera
You have used the wrong object while mocking. You should be using Structure2Root rather then Structure1Root
The correct test class is below which is working perfectly fine.
package com.demo.samples.tdd;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import com.demo.samples.tdd.responses.Product;
import com.demo.samples.tdd.responses.Structure2Root;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentMatchers;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
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.setup.MockMvcBuilders;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.context.WebApplicationContext;
import com.demo.samples.tdd.responses.Category;
import com.demo.samples.tdd.responses.Structure1Root;
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = WebEnvironment.MOCK, classes = TestDrivenDevelopmentWithJavaApplication.class)
public class TestDrivenDevelopmentWithJavaApplicationTests {
#Autowired
private WebApplicationContext webApplicationContext;
private MockMvc mockMvc;
#MockBean
private RestTemplate client;
#Before
public void setup() throws Exception {
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
// Structure1Root category = new Structure1Root();
// Category cat = new Category();
// cat.setCategoryName("Test1");
// cat.setDescription("Test");
// category.setD(cat);
//
// Mockito.when(client.exchange(
// ArgumentMatchers.eq("https://services.odata.org/V2/Northwind/Northwind.svc/Products(1)?$format=json"),
// ArgumentMatchers.eq(HttpMethod.GET), ArgumentMatchers.eq(null),
// ArgumentMatchers.eq(Structure1Root.class)))
// .thenReturn(new ResponseEntity<Structure1Root>(category, HttpStatus.OK));
Structure2Root category2 = new Structure2Root();
Product product = new Product();
product.setProductName("Test1");
product.setUnitPrice("1");
category2.setD(product);
Mockito.when(client.exchange(
ArgumentMatchers.eq("https://services.odata.org/V2/Northwind/Northwind.svc/Products(1)?$format=json"),
ArgumentMatchers.eq(HttpMethod.GET), ArgumentMatchers.eq(null),
ArgumentMatchers.eq(Structure2Root.class)))
.thenReturn(new ResponseEntity<Structure2Root>(category2, HttpStatus.OK));
}
#Test
public void testendpoint1() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("/api/endpoint1?token=1").contentType(MediaType.APPLICATION_JSON))
.andExpect(content().string(org.hamcrest.Matchers.containsString("Test1")));
}
}

Resources