test containers testing in spring s3 service errors - spring-boot

I have set up s3 container, and have a basic test working. What i wanted to know was how can i test it throws AwsServiceException. Is there anyway i can get the container to do that.
#Testcontainers
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class TestContainerSetUp {
protected static final String BUCKET_NAME = "testbucket";
#Container
public static final S3MockContainer s3MockContainer = new S3MockContainer(DockerImageName.parse("adobe/s3mock:2.4.13"))
.withInitialBuckets(BUCKET_NAME);

Testcontainers ToxiProxy module can be used here. See an example here. Client should point to the proxy created via toxiproxy and then you can simulate timeouts, latency, and soon. Read more about toxiproxy here.

Related

Spring Boot / Junit5+Mockito: Mock's mockitoInterceptor gets replaced during test?

I have two service classes (there are more, of course, but those two are relevant here), which are in use during an integration test.
For test, I set up a mock (ConfigurationService) and stub two methods:
#ExtendWith(SpringExtension.class)
#ActiveProfiles("test")
#SpringBootTest
#TestPropertySource(properties =
"spring.main.allow-bean-definition-overriding=true")
public class VehicleAuditExecutionServiceIT {
#MockBean
private ConfigurationService configurationServiceMock;
#Autowired
private VehicleAuditExecutionService vehicleAuditExecutionService;
#Test
void testExecuteVehicleAudits() throws IOException {
// quite some DB operations for the test setup here
AuditDurationConfigDTO auditDurationConfigDTO = new AuditDurationConfigDTO();
auditDurationConfigDTO.setMaxDuration(deMaxDuration);
Map<String, ScheduledAuditConfigDTO> map = new HashMap<>();
map.put(country, ScheduledAuditConfigDTO.builder()
.groupCalculationEnabled(false)
.build());
when(configurationServiceMock.getAuditDurationConfig(country)).thenReturn(auditDurationConfigDTO);
when(configurationServiceMock.getScheduledAuditConfigurationForCountry(country)).thenReturn(ScheduledAuditConfigDTO.builder()
.groupCalculationEnabled(false)
.sameAuditDurationAllDealersSameGroupId(false)
.build());
vehicleAuditExecutionService.executeVehicleAudits(startDate, country);
verify(publishAuditInterfaceMock).pushExecutions(country, dealerDe1ExportAuditDtoList, auditCategory);
}
#ComponentScan(basePackages = "com.application")
#SpringBootApplication
#PropertySource("classpath:application-test.yml")
static class TestConfiguration {}
}
After the setup, the stubbings are available:
During the test's execution, vehicleAuditExecutionService.executeVehicleAudits(startDate, country) calls the AuditPreparationService, which in turn uses the configurationServiceMock (using #Autowired constructor injection). As expected, the calls gets matched and result set up is returned.
Later, the execution returns to vehicleAuditExecutionService.executeVehicleAudits(startDate, country), where it calls the configurationServiceMock (#Autowired constructor injection, as well) again. But here, the mock's configuration has been changed: the mock's attribute mockitoInterceptor gets replaced by some other instance.
Result: the stubbing is gone and the call returns null - leading to a NPE.
The screenshots were taken using org.springframework.boot:spring-boot-starter-parent:2.7.6, but I've tried that with multiple Spring Boot versions:
2.7.0
2.6.14
2.5.14
2.4.13
2.3.12.RELEASE
Each version has this issue - but I've never seen it in any other test. So I guess, there's something wrong with my test setup - but I cannot spot it.
Any idea, why this is happening?
Thanks a lot - please do not hesitate to ask for any further information, if needed for analysis.
kniffte

Using Spring repository in static methods for setting up test data

In order to setup test data for my Spring Boot integration tests, I'd like to create some helper classes and methods which populate the data using the repositories.
Here is an example:
#Component
public class TestUtils {
private static TemplateRepository templateRepository;
#Autowired
public TestUtils(TemplateRepository templateRepository) {
TestUtils.templateRepository = templateRepository;
}
public static void createTemplates() {
Template template = Template.builder()
.content("some content")
.build();
templateRepository.save(template);
}
}
Due to a lack of experience, I cannot tell if this approach is fine. It it "safe" to inject the repository as static? Or are there better approaches for setting up test data?
Don't use static. If you want to use Java to initialize the data in the repository, just do so in your test.
What you can do if you need to create a few things in different repositories is create a dedicated component:
#Component
public class DatabaseInitializer {
private final TemplateRepository templateRepository;
private final MyOtherRepository myOtherRepository;
// Add constructor here
public void createInitialData() {
// Use repositories to persist some data
}
#ExtendWith(SpringExtension.class)
#Import(DatabaseInitializer.class)
class MyTest {
#Autowired
private DatabaseInitializer initDb;
#Test
void myTest() {
initDb.createInitialData(); // Or put this in a `#Before..` method
// actual test here
}
}
I use TestContainers and Flyway.
You can make SQL scripts and annotate test methods with #Sql and provide a .sql file and/or statements to be run.
You can store these .sql files in the test/resources folder.
Loading Initial Test Data
There is a very well explained process to initialize the data in docs. I would advice you to refer below
https://docs.spring.io/spring-boot/docs/current/reference/html/howto.html#howto.data-initialization
You just have to manintain Insert statements in predefined sql files.

How can access the local.server.port when SpringBootTest.WebEnvironment.RANDOM_PORT in test

I am trying to setup feign client for Spock test with in the test of the spring boot application.
The Spock test is setup with
#ActiveProfiles("functional-test")
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
I have a application-functional-test.yml where my feign client is url define, I tried:
my.feign.client.example.url: localhost:${local.server.port}
But local server port says 0, which doesn't fit the random port for the application.
I also tried this where randomServerPort contains the random port, but I can't override the property:
#LocalServerPort
int randomServerPort;
#Value('${my.feign.client.example.url}')
String feignTestClient
void setup() {
feignTestClient="localhost:${randomServerPort}"
}
Any ideas for a best practice? I prefer that the url is setup within application-functional-test.yml so avoid initializing with dummy value
My Feign client look like this:
#FeignClient(name = "myClient", url = "${my.feign.client.example.url}")
public interface FeignTestRestClient extends SomeControllerApi {}

spring cloud is failing when I try to mock sleuth in unit tests

I am facing some error when I try to mock objects like Tracer and Span in unit tests if I use Dalston.SR3 or Dalston.Release versions, but this problem doesn't happen if I use Camden.SR6 or Camden.SR7 versions.
Find an example code here
Microservice msvc-a is using Dalston version and has two test classes, where only is failing the class where I am trying to mock the Tracer and the Span objects.
Microservice msvc-b is using Camden version and has the same test classes.
At the same time, I can't understand this situation when I am in debug mode in STS and why I can't see any error trace or something like that... only a NullpointerException.
public class AbstractSpanAccessorTest {
#MockBean
private Tracer tracer;
#MockBean
private Span span;
private Random random = new Random();
#Before
public void mockSpan() {
long id = createId();
Span spanMock = Span.builder().name("mock").traceId(id).spanId(id).build();
doReturn(spanMock.traceIdString()).when(span).traceIdString();
doReturn(span).when(tracer).getCurrentSpan();
doReturn(span).when(tracer).createSpan(anyString());
}
private long createId() {
return random.nextLong();
}
}
It was my mistake. The correct way to mock the Span is:
#Before
public void mockSpan() {
long id = createId();
span = Span.builder().name("mock").traceId(id).spanId(id).build();
doReturn(span).when(tracer).getCurrentSpan();
doReturn(span).when(tracer).createSpan(anyString());
}
Tracer wouldn't mock at all with Finchley.SR2 so I ended up with this:
Tracing tracing = Tracing.newBuilder().build();
Tracer tracer = tracing.tracer();

What is the best way to test that a spring application context fails to start?

I use the spring-boot-starter-web and spring-boot-starter-test.
Let's say I have a class for binding configuration properties:
#ConfigurationProperties(prefix = "dummy")
public class DummyProperties {
#URL
private String url;
// getter, setter ...
}
Now I want to test that my bean validation is correct. The context should fail to start (with a specfic error message) if the property dummy.value is not set or if it contains an invalid URL. The context should start if the property contains a valid URL. (The test would show that #NotNull is missing.)
A test class would look like this:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = MyApplication.class)
#IntegrationTest({ "dummy.url=123:456" })
public class InvalidUrlTest {
// my test code
}
This test would fail because the provided property is invalid. What would be the best way to tell Spring/JUnit: "yep, this error is expected". In plain JUnit tests I would use the ExpectedException.
The best way to test Spring application context is to use ApplicationContextRunner
It is described in Spring Boot Reference Documentation:
https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-developing-auto-configuration.html#boot-features-test-autoconfig
And there is a quick guide about it:
https://www.baeldung.com/spring-boot-context-runner
Sample usage
private static final String POSITIVE_CASE_CONFIG_FILE =
"classpath:some/path/positive-case-config.yml";
private static final String NEGATIVE_CASE_CONFIG_FILE =
"classpath:some/path/negative-case-config.yml";
#Test
void positiveTest() {
ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withInitializer(new ConfigDataApplicationContextInitializer())//1
.withInitializer(new ConditionEvaluationReportLoggingListener(LogLevel.DEBUG))//2
.withUserConfiguration(MockBeansTestConfiguration.class)//3
.withPropertyValues("spring.config.location=" + POSITIVE_CASE_CONFIG_FILE)//4
.withConfiguration(AutoConfigurations.of(BookService.class));//5
contextRunner
.run((context) -> {
Assertions.assertThat(context).hasNotFailed();//6
});
}
#Test
void negativeTest() {
ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withInitializer(new ConfigDataApplicationContextInitializer())//1
.withInitializer(new ConditionEvaluationReportLoggingListener(LogLevel.DEBUG))//2
.withUserConfiguration(MockBeansTestConfiguration.class)//3
.withPropertyValues("spring.config.location=" + NEGATIVE_CASE_CONFIG_FILE)//4
.withConfiguration(AutoConfigurations.of(BookService.class));//5
contextRunner
.run((context) -> {
assertThat(context)
.hasFailed();
assertThat(context.getStartupFailure())
.isNotNull();
assertThat(context.getStartupFailure().getMessage())
.contains("Some exception message");
assertThat(extractFailureCauseMessages(context))
.contains("Cause exception message");
});
}
private List<String> extractFailureCauseMessages(AssertableApplicationContext context) {
var failureCauseMessages = new ArrayList<String>();
var currentCause = context.getStartupFailure().getCause();
while (!Objects.isNull(currentCause)) {//7
failureCauseMessages.add(currentCause.getMessage());
currentCause = currentCause.getCause();
}
return failureCauseMessages;
}
Explanation with examples of similar definitions from Junit5 Spring Boot Test Annotations:
Triggers loading of config files like application.properties or application.yml
Logs ConditionEvaluationReport using given log level when application context fails
Provides class that specifies mock beans, ie. we have #Autowired BookRepository in our BookService and we provide mock BookRepository in MockBeansTestConfiguration. Similar to #Import({MockBeansTestConfiguration.class}) in test class and #TestConfiguration in class with mock beans in normal Junit5 Spring Boot Test
Equivalent of #TestPropertySource(properties = { "spring.config.location=" + POSITIVE_CASE_CONFIG_FILE})
Triggers spring auto configuration for given class, not direct equivalent, but it is similar to using #ContextConfiguration(classes = {BookService.class}) or #SpringBootTest(classes = {BookService.class}) together with #Import({BookService.class}) in normal test
Assertions.class from AssertJ library, there should be static import for Assertions.assertThat, but I wanted to show where this method is from
There should be static import for Objects.isNull, but I wanted to show where this method is from
MockBeansTestConfiguration class:
#TestConfiguration
public class MockBeansTestConfiguration {
private static final Book SAMPLE_BOOK = Book.of(1L, "Stanisław Lem", "Solaris", "978-3-16-148410-0");
#Bean
public BookRepository mockBookRepository() {
var bookRepository = Mockito.mock(BookRepository.class);//1
Mockito.when(bookRepository.findByIsbn(SAMPLE_BOOK.getIsbn()))//2
.thenReturn(SAMPLE_BOOK);
return bookRepository;
}
}
Remarks:
1,2. There should be static import, but I wanted to show where this method is from
Why is that an integration test to begin with? Why are you starting a full blown Spring Boot app for that?
This looks like unit testing to me. That being said, you have several options:
Don't add #IntegrationTest and Spring Boot will not start a web server to begin with (use #PropertySource to pass value to your test but it feels wrong to pass an invalid value to your whole test class)
You can use spring.main.web-environment=false to disable the web server (but that's silly given the point above)
Write a unit test that process that DummyProperties of yours. You don't even need to start a Spring Boot application for that. Look at our own test suite
I'd definitely go with the last one. Maybe you have a good reason to have an integration test for that?
I think the easiest way is:
public class InvalidUrlTest {
#Rule
public DisableOnDebug testTimeout = new DisableOnDebug(new Timeout(5, TimeUnit.SECONDS));
#Rule
public ExpectedException expected = ExpectedException.none();
#Test
public void shouldFailOnStartIfUrlInvalid() {
// configure ExpectedException
expected.expect(...
MyApplication.main("--dummy.url=123:456");
}
// other cases
}

Resources