The module sda-commons-web-testing
provides a few helpers to support integration tests related to the features provided by SDA Spring
Boot Commons and propagated patterns of SDA SE.
The default configuration of Spring's Actuator with this library is a separate management port.
In tests, this causes some issues that
need configuration.
@SpringBootTest's need to be marked with @DirtiesContext or define management.server.port=0
either in @SpringBootTest(properties = "…") or in src/test/resources/application.properties.
The security concept of SDA SE has a strict separation of authentication
and authorization.
The identity of a user is verified in the application by validating a JWT from an OIDC provider.
The authorization is delegated to an Open Policy Agent sidecar with policies for an actual
environment.
The service implementation grants access based on constraints received from the policy, not on roles
which may be different in each environment or not even available.
sda-commons-web-testing provides ApplicationContextInitializers for integration test contexts to
work with authenticated users and mocked authorization decisions, including constraints as well as
for disabling the security mechanism.
Mocking Authentication and Authorization Constraints
Usually constraints are handled in @Controllers who call the allowed business functions with
applicable parts of the constraints model.
Integration tests with HTTP calls may not be the best solution to test complex logic on constraints.
In such cases the constraints model can be mocked.
Mocking Constraints for Unit Tests Compared to Integration Test
Generating and Validating up-to-date Documentation¶
At SDA SE it is common to publish generated documentation like OpenAPI or AsyncAPI in the repository.
From there it's picked up by our developer portal based on Backstage.
sda-commons-web-testing provides GoldenFileAssertions to validate in a test that such
documentation is up-to-date and updates it when run locally.
It is recommended to test with an embedded MongoDB using Flapdoodle's Spring Boot module
de.flapdoodle.embed:de.flapdoodle.embed.mongo.spring3xand the SDA Spring Boot Commons testing
module.
The version is managed by the library as described above.
Flapdoodle will start a MongoDB server and configures the connection for Spring Boot tests.
The MongoDB version can be selected with (test) application properties:
1
de.flapdoodle.mongodb.embedded.version=4.4.1
To skip Flapdoodle's autoconfiguration and use a provided database (e.g. an AWS DocumentDB), the
property test.mongodb.connection.string (or environment variable TEST_MONGODB_CONNECTION_STRING)
can be used to provide a complete MongoDB Connection String.
This feature is provided by the SDA Spring Boot Commons testing module and is only activated when
Flapdoodle's autoconfiguration is available for the test setup.
Clean up of the collections is important, especially when using a provided database, that may be
reused in a subsequent build.
To ensure backwards compatibility of the database after upgrades, refactorings or changing the
database framework, asserting with a bare MongoClient removes influence of mappers and converters.
It is recommended to test with an embedded Kafka using the Spring Boot module
org.springframework.kafka:spring-kafka-test and the SDA Spring Boot Commons testing module.
The version is managed by the library as described above.
import staticorg.awaitility.Awaitility.await;import staticorg.mockito.Mockito.verify;importjava.util.Map;importorg.junit.jupiter.api.Test;importorg.sdase.commons.spring.boot.kafka.test.KafkaTestApp;importorg.sdase.commons.spring.boot.kafka.test.SomeService;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.beans.factory.annotation.Value;importorg.springframework.boot.test.context.SpringBootTest;importorg.springframework.boot.test.mock.mockito.SpyBean;importorg.springframework.kafka.core.KafkaTemplate;importorg.springframework.kafka.test.context.EmbeddedKafka;@SpringBootTest(classes=KafkaTestApp.class,webEnvironment=SpringBootTest.WebEnvironment.MOCK,properties={"management.server.port=0"})@EmbeddedKafkaclassKafkaConsumerIntegrationTest{@AutowiredKafkaTemplate<String,Object>kafkaTemplate;@SpyBeanSomeServicesomeService;@Value("${app.kafka.consumer.topic}")// same as configured for the consumerprivateStringtopic;@TestvoidshouldDoSomethingWithMessage(){StringgivenMessageKey="some-key";vargivenMessageValue=Map.of("property","value");kafkaTemplate.send(topic,givenMessageKey,givenMessageValue);// verify that a repository saved data derived from the message, an external service is// called or whatever should happen when the message is receivedawait().untilAsserted(()->verify(someService).didTheJob(givenMessageValue));}}
import staticorg.assertj.core.api.Assertions.assertThat;importjava.time.Duration;importjava.util.Set;importjava.util.concurrent.ExecutionException;importjava.util.concurrent.TimeoutException;importorg.apache.kafka.clients.consumer.ConsumerRecord;importorg.apache.kafka.clients.consumer.ConsumerRecords;importorg.apache.kafka.clients.consumer.KafkaConsumer;importorg.apache.kafka.common.serialization.StringDeserializer;importorg.junit.jupiter.api.Test;importorg.sdase.commons.spring.boot.kafka.test.KafkaTestApp;importorg.sdase.commons.spring.boot.kafka.test.KafkaTestModel;importorg.sdase.commons.spring.boot.kafka.test.KafkaTestProducer;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.beans.factory.annotation.Value;importorg.springframework.boot.test.context.SpringBootTest;importorg.springframework.kafka.test.EmbeddedKafkaBroker;importorg.springframework.kafka.test.context.EmbeddedKafka;importorg.springframework.kafka.test.utils.KafkaTestUtils;@SpringBootTest(classes=KafkaTestApp.class,webEnvironment=SpringBootTest.WebEnvironment.MOCK,properties={"management.server.port=0"})@EmbeddedKafkaclassKafkaProducerIntegrationTest{@AutowiredEmbeddedKafkaBrokerkafkaEmbedded;@AutowiredKafkaTestProducerkafkaTestProducer;@Value("${app.kafka.producer.topic}")// as defined for the producerStringtopic;@TestvoidshouldProduceMessage()throwsExecutionException,InterruptedException,TimeoutException{KafkaTestModelgiven=newKafkaTestModel().setCheckInt(1).setCheckString("Hello World!");kafkaTestProducer.send(given);varactualRecords=consumeRecords(topic);assertThat(actualRecords).hasSize(1).first().extracting(ConsumerRecord::value).asString().contains("Hello World!");}ConsumerRecords<String,String>consumeRecords(Stringtopic){try(vartestConsumer=createTestConsumer(topic)){returntestConsumer.poll(Duration.ofSeconds(10));}}KafkaConsumer<String,String>createTestConsumer(Stringtopic){KafkaConsumer<String,String>consumer=newKafkaConsumer<>(KafkaTestUtils.consumerProps(kafkaEmbedded.getBrokersAsString(),"test-consumer","true"),newStringDeserializer(),newStringDeserializer());consumer.subscribe(Set.of(topic));returnconsumer;}}
sda-commons-web-testing provides the annotation S3Test to start a local S3 mock with
Robothy local-s3 and configure Spring Boot as needed for
sda-commons-starter-s3.
@S3Test must be placed before @SpringBootTest if used for a full integration test with an
application context.
The dependency io.github.robothy:local-s3-rest must be added as test dependency to the project.
software.amazon.awssdk:s3 is needed as well and comes with io.awspring.cloud:spring-cloud-aws-s3
via sda-commons-starter-s3.
The versions are managed by the library as described above.
S3Test can also be used in tests without a Spring Context.
Test, set up and tear down methods can request S3Client and LocalS3Configuration as
method parameter to interact with the S3 mock or set up the tested services.