Problem: Need a typical modern Spring Boot application to describe various scenarios and provide solutions for common problems.
Solution: Repository with a fresh Spring Boot application covering various scenarios.
Status: Completed, article is published, currently using.
Testcontainers configuration
for postgres
using @ServiceConnection
feature from spring.
Code related to article, where I would like to describe an approach to writing tests with a clear division into separate stages, each performing its specific role. This facilitates the creation of tests that are easier to read, understand, and maintain.
The discussion will focus on using the Arrange-Act-Assert methodology for integration testing in the Spring Framework with mocking of HTTP requests to external resources encountered during the execution of the tested code within the system behavior. The tests under consideration are written using the Spock Framework in the Groovy language. MockRestServiceServer will be used as the mocking mechanism. There will also be a few words about WireMock.
Article on Habr - Разносим по полочкам этапы тестирования http запросов в Spring
Article on Medium - Ordering Chaos: Arranging HTTP Request Testing in Spring
Offers practical recommendations for writing integration tests, demonstrating how to focus on the specifications of interactions with external services, making the tests more readable and easier to maintain. The approach not only enhances the efficiency of testing but also promotes a better understanding of the integration processes within the application. Through the lens of specific examples, various strategies and tools — such as DSL wrappers, JsonAssert, and Pact — will be explored, offering the reader a comprehensive guide to improving the quality and visibility of integration tests.
Article on Habr - Повышение наглядности интеграционных тестов
Article on Medium - Enhancing the Visibility of Integration Tests
The RequestCaptor
class is designed to capture HTTP requests in tests. This class keeps track of how many times it
has matched a request (times), the body of the last request both as a string (bodyString) and as a parsed
JSON object (body), and the headers of the last request (headers).
Please refer to the test below to get an insight.
def "Forecast for provided city London is 42"() {
setup: // (1)
def requestCaptor = new RequestCaptor()
mockServer.expect(manyTimes(), requestTo("https://external-weather-api.com/forecast")) // (2)
.andExpect(method(HttpMethod.POST))
.andExpect(requestCaptor) // (3)
.andRespond(withSuccess('{"result": "42"}', MediaType.APPLICATION_JSON)); // (4)
when: // (5)
def forecast = weatherService.getForecast("London")
then: // (6)
forecast == "42"
requestCaptor.times == 1 // (7)
requestCaptor.body.city == "London" // (8)
requestCaptor.headers.get("Content-Type") == ["application/json"]
}
Include the necessary dependency in your project's build configuration to utilize Request Captor:
testImplementation 'pw.avvero:request-captor:1.0.0'
The WiredRequestCaptor
class is designed to capture HTTP requests in tests. This class keeps track of how many times it
has matched a request (times), the body of the last request both as a string (bodyString) and as a parsed
JSON object (body), and the headers of the last request (headers).
Please refer to the test below to get an insight.
def "Forecast for provided city London is 42"() {
setup:
StubMapping forecastMapping = wireMockServer.stubFor(WireMock.post(WireMock.urlEqualTo("/forecast"))
.willReturn(WireMock.aResponse()
.withBody('{"result": "42"}')
.withStatus(200)
.withHeader("Content-Type", "application/json")))
def requestCaptor = new WiredRequestCaptor(wireMockServer, forecastMapping)
when:
def forecast = weatherService.getForecast("London")
then:
forecast == "42"
requestCaptor.times == 1
requestCaptor.body.city == "London"
requestCaptor.headers.get("Content-Type") == ["application/json"]
}
Include the necessary dependency in your project's build configuration to utilize Request Captor:
testImplementation 'pw.avvero:request-captor-wired:1.0.0'
In our continuous effort to optimize our test suite, a significant resource-intensive method was identified during profiling. This method is related to the PostgreSQL JDBC driver's password protection mechanism during authentication. By modifying our PostgreSQL container's environment settings, we can achieve a noteworthy performance improvement.
The profiling session pinpointed a method consuming considerable resources:
java.lang.invoke.VarHandleByteArrayAsInts$ArrayHandle.index
org.postgresql.shaded.com.ongres.scram.common.util.CryptoUtil.hi
org.postgresql.shaded.com.ongres.scram.common.ScramMechanisms.saltedPassword
These methods are part of the PostgreSQL JDBC driver, specifically involved in password protection during the authentication process.
To bypass this resource-intensive password protection, we can disable it by setting an environment variable in our PostgreSQL container definition. This change instructs the container to use the "trust" authentication method, eliminating the need for password verification.
Modify the PostgreSQL container configuration as follows:
private final static PostgreSQLContainer<?> POSTGRES = new PostgreSQLContainer<>(DockerImageName.parse("postgres:14"))
.withEnv("POSTGRES_HOST_AUTH_METHOD", "trust") // Disable password protection
.waitingFor(Wait.forListeningPort());
A synthetic benchmark demonstrated a performance improvement when using the "trust" authentication method:
Benchmark Mode Cnt Score Error Units
PostgresqlContainerAuthModeBenchmark.methodDefaultOneConnection ss 4 1,056 ± 0,050 s/op
PostgresqlContainerAuthModeBenchmark.methodTrustOneConnection ss 20 1,141 ± 0,082 s/op
PostgresqlContainerAuthModeBenchmark.methodDefaultOneHundredConnection ss 14 2,201 ± 0,095 s/op
PostgresqlContainerAuthModeBenchmark.methodTrustOneHundredConnection ss 20 1,635 ± 0,069 s/op