Skip to content

Commit b04b3e1

Browse files
yauheni-bbtarun-bb
andauthored
Working with testcontainers (#109)
Working with Testcontainers --------- Co-authored-by: Tarun Bharti <72968092+tarun-bb@users.noreply.github.com>
1 parent 1b7b760 commit b04b3e1

File tree

7 files changed

+181
-0
lines changed

7 files changed

+181
-0
lines changed

angular.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,5 +174,8 @@
174174
}
175175
}
176176
}
177+
},
178+
"cli": {
179+
"analytics": false
177180
}
178181
}

content/authors/authors.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,10 @@
8383
"avatar": "anjali_goyal.jpg",
8484
"role": "Senior QA Engineer - Customer Success"
8585
},
86+
"Yauheni Navosha": {
87+
"avatar": "yn.png",
88+
"role": "Backend Engineer - Customer Success"
89+
},
8690
"Furkan Aksin": {
8791
"avatar": "furkan.jpg",
8892
"role": "Backend Engineer - Customer Success"

content/authors/avatars/yn.png

393 KB
Loading
Loading
Loading
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
# Working with Testcontainers
2+
3+
4+
Framework that simplifies the process of setting up, managing, and tearing down containerized environments for integration testing.
5+
6+
7+
![](assets/testcontainers.png)
8+
9+
10+
Authors: Yauheni Navosha
11+
Date: unpublished
12+
Category: backend
13+
14+
15+
tags: backend, tests, testing, integration testing, docker, containers
16+
17+
---
18+
19+
## What is Testcontainers?
20+
21+
Testcontainers is a testing library integration tests with real services wrapped in Docker containers. Using Testcontainers, you can write tests by directly interacting with the same kind of services utilized in production, without relying on mocks or in-memory services.
22+
23+
## When to use it?
24+
25+
Testcontainers simplifies running of integration tests that involve external dependencies such as databases, message queues, etc., by providing a convenient way to spin up disposable instances of these dependencies within your test environment.
26+
27+
## Why should you use testcontainers instead of mocked and in-memory services?
28+
29+
- **In-memory services may lack functionalities present in your production service.** For example: to enable integration testing with Postgres/Oracle databases, one might use an in-memory H2 database. But H2 might not support some MySQL/Oracle specific features. That might lead to worse quality of tests and forcing to consider using of the feature at all.
30+
- **In-memory services and mocks might delay the feedback cycle.** For example: despite successful testing with an H2 database, you may not discover unexpected issues with the SQL query syntax before deployment. It might happen with mocking APIs, when it does not reflect real-world compatability.
31+
32+
## What are drawbacks?
33+
34+
Each technology has benefits and drawback and Testcontainers is not an exception. Creation of containerized service is more expensive from the perspective of startup time and resources required for running the service
35+
in comparison to in-memory and mocking solutions. Therefore, time for integration test execution may be increased. Also, running of the tests may require additional CPU and RAM for a continuous integration node.
36+
37+
## How to use it?
38+
39+
This example demonstrates an integration test utilizing Testcontainers with MySQL and Kafka.
40+
41+
Let’s consider the following scenario:
42+
a product resides in the MySQL database. A service consumes Kafka message, processes it and update the product in MySQL database according to the message payload.
43+
44+
![](assets/diagram.png)
45+
46+
### Getting started
47+
Firstly, installing, and configuring a Docker runtime [supported](https://java.testcontainers.org/supported_docker_environment/) by Testcontainers is necessary.
48+
49+
Next, you need to add some dependencies to use Testcontainers:
50+
51+
```xml
52+
<dependency>
53+
<groupId>org.springframework.boot</groupId>
54+
<artifactId>spring-boot-testcontainers</artifactId>
55+
<scope>test</scope>
56+
</dependency>
57+
58+
59+
<dependency>
60+
<groupId>org.testcontainers</groupId>
61+
<artifactId>junit-jupiter</artifactId>
62+
<scope>test</scope>
63+
</dependency>
64+
```
65+
66+
Also, dependencies for using Testcontainers with MySQL and Kafka:
67+
68+
```xml
69+
<dependency>
70+
<groupId>org.testcontainers</groupId>
71+
<artifactId>kafka</artifactId>
72+
<scope>test</scope>
73+
</dependency>
74+
<dependency>
75+
<groupId>org.testcontainers</groupId>
76+
<artifactId>mysql</artifactId>
77+
<scope>test</scope>
78+
</dependency>
79+
```
80+
81+
Some ready-to-use testcontainers are available (Mysql, Kafka,…) but if you can’t find your desired module you can use any custom image that you want.
82+
83+
You can find ready-to-use modules on the [Testcontainers website](https://testcontainers.com/).
84+
85+
86+
### Write the integration test
87+
88+
89+
```java
90+
@SpringBootTest
91+
@TestPropertySource(
92+
properties = {
93+
"spring.kafka.consumer.auto-offset-reset=earliest",
94+
"spring.datasource.url=jdbc:tc:mysql:8.0.32:///db",
95+
}
96+
)
97+
@Testcontainers
98+
@ActiveProfiles("it")
99+
@Slf4j
100+
public class ProductPriceChangedEventHandlerTest {
101+
@Container
102+
static final KafkaContainer kafka = new KafkaContainer(
103+
DockerImageName.parse("confluentinc/cp-kafka:7.5.1")
104+
);
105+
106+
107+
@DynamicPropertySource
108+
static void overrideProperties(DynamicPropertyRegistry registry) {
109+
registry.add("spring.kafka.bootstrap-servers", kafka::getBootstrapServers);
110+
}
111+
112+
113+
@Autowired
114+
private EventEmitter<ProductPriceChangedEvent> productPriceChangedEventEventEmitter;
115+
116+
117+
@Autowired
118+
private ProductRepository productRepository;
119+
120+
121+
@BeforeEach
122+
void setUp() {
123+
Product product = new Product(null, "P100", "Product One", BigDecimal.TEN);
124+
productRepository.save(product);
125+
}
126+
127+
128+
@Test
129+
void shouldHandleProductPriceChangedEvent() {
130+
ProductPriceChangedEvent event = new ProductPriceChangedEvent();
131+
event.withPrice("14.50")
132+
.withCode("P100");
133+
134+
135+
productPriceChangedEventEventEmitter.sendMessage(event);
136+
137+
138+
await()
139+
.pollInterval(Duration.ofSeconds(3))
140+
.atMost(10, SECONDS)
141+
.untilAsserted(() -> {
142+
Optional<Product> optionalProduct = productRepository.findByCode("P100");
143+
144+
145+
assertThat(optionalProduct).isPresent();
146+
log.info("Product {}", optionalProduct.get());
147+
assertThat(optionalProduct.get().getCode()).isEqualTo("P100");
148+
assertThat(optionalProduct.get().getPrice())
149+
.isEqualTo(new BigDecimal("14.50"));
150+
});
151+
}
152+
}
153+
```
154+
155+
156+
* `@SpringBootTest` load the complete Spring app context
157+
* The Testcontainers special JDBC URL exists to spin up MySQL container and configure it as a DataSource with Spring Boot app context
158+
* Testcontainers JUnit 5 Extension annotations @Testcontainers and @Container annotations spin up a Kafka container and register the bootstrap-servers location using DynamicPropertySource mechanism.
159+
* Created a Product record in the database before running the test using the @BeforeEach callback method.
160+
* During the test `EventEmitter` sends messages to Kafka.
161+
* As Kafka message processing is an asynchronous process, the Awaitility library checks updates for the product price in the database to the expected value or not with an interval of 3 seconds waiting up to 10 seconds. If the message gets consumed and processed within 10 seconds, the test passes; otherwise, it fails.
162+
* Also, notice that the property `spring.kafka.consumer.auto-offset-reset` has the value `earliest` so that the listener consume the messages even if the sender dispatches the message to the topic before the listener is ready. This setting is helpful for running tests.
163+
164+
## Conclusion
165+
Testcontainers emerges as a powerful tool for ensuring reliable and robust testing environments. Unlike mocked and in-memory services,
166+
Testcontainers offers the advantage of real-world compatibility, accurately reflecting the behavior of external dependencies such as databases, APIs, message queues.
167+
By utilizing Testcontainers, developers can identify compatibility issues in the development process at its outset, leading to more resilient software deployments,
168+
enhance the effectiveness of testing and improve the reliability of software apps.
169+
170+
## References
171+
- [Testcontainers official documentation](https://testcontainers.com/)
172+
- [Awaitility](http://www.awaitility.org/)
173+
- [An example of using testcontainers](https://github.com/Backbase/working-with-testcontainers)

routes.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
/2023/12/13/angular-micro-frontends
2121
/principles/innersource
2222
/principles/software-engineering
23+
/unpublished/working-with-test-containers
2324
/unpublished/liquibase-as-an-init-container
2425
/category/tech-life
2526
/category/devops

0 commit comments

Comments
 (0)