Skip to content

Commit 551fe7d

Browse files
committed
Update project to auto-clone at startup. Few other minor updates.
1 parent f3845b7 commit 551fe7d

9 files changed

Lines changed: 139 additions & 79 deletions

compose.override.yaml

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ services:
1010
configs:
1111
- source: docker-java-properties
1212
target: /home/coder/.docker-java.properties
13+
- source: clone-repo-script
14+
target: /entrypoint.d/clone-repo.sh
15+
mode: 0755
1316

1417
# # Override the default ports to publish additional ports you may need
1518
# # More details - https://github.com/dockersamples/labspace-infra/blob/main/docs/configuration.md#overriding-the-workspace-ports
@@ -26,4 +29,16 @@ services:
2629
configs:
2730
docker-java-properties:
2831
content: |
29-
api.version=1.48
32+
api.version=1.48
33+
clone-repo-script:
34+
content: |
35+
#!/bin/bash
36+
set -e
37+
38+
if [ ! -d "/home/coder/project/.git" ]; then
39+
find "/home/coder/project" -type f -name '.*' -exec rm '{}' \;
40+
git clone https://github.com/testcontainers/workshop.git /home/coder/project
41+
rm /home/coder/project/step-*.md
42+
else
43+
echo "Repository already cloned, skipping."
44+
fi

compose.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
include:
2-
- oci://dockersamples/labspace-content-dev:latest-sdlc
2+
- oci://dockersamples/labspace-content-dev
33
- ./compose.override.yaml

labspace/01-introduction.md

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,22 +10,20 @@ By the end, you will have learned the following:
1010

1111
## Setup
1212

13-
1. Clone the workshop repository into the lab environment:
14-
15-
```bash
16-
git clone https://github.com/testcontainers/workshop.git
17-
```
18-
19-
2. Build the project to download the dependencies
20-
21-
Switch to the workshop folder:
22-
23-
```bash
24-
cd workshop
25-
```
26-
27-
3. Build the project with Maven:
13+
1. Run a build to download all of the dependencies and validate the application builds successfully:
2814

2915
```bash
3016
./mvnw verify
3117
```
18+
19+
If successful, you should see output similar to the following:
20+
21+
```console no-copy-button no-run-button
22+
[INFO] The original artifact has been renamed to /home/coder/project/target/demo-0.0.1-SNAPSHOT.jar.original
23+
[INFO] ------------------------------------------------------------------------
24+
[INFO] BUILD SUCCESS
25+
[INFO] ------------------------------------------------------------------------
26+
[INFO] Total time: 27.838 s
27+
[INFO] Finished at: 2026-03-20T19:41:38Z
28+
[INFO] ------------------------------------------------------------------------
29+
```

labspace/02-exploring-the-app.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,23 +10,23 @@ When a rating is submitted, we must verify that the talk for the given ID is pre
1010

1111
Our database of choice is PostgreSQL, accessed with Spring JDBC.
1212

13-
Check :fileLink[TalksRepository]{path="workshop/src/main/java/com/example/demo/repository/TalksRepository.java"}.
13+
Check :fileLink[TalksRepository]{path="src/main/java/com/example/demo/repository/TalksRepository.java"}.
1414

1515
### Redis
1616

1717
We store the ratings in Redis database with Spring Data Redis.
1818

19-
Check :fileLink[RatingsRepository]{path="workshop/src/main/java/com/example/demo/repository/RatingsRepository.java"}.
19+
Check :fileLink[RatingsRepository]{path="src/main/java/com/example/demo/repository/RatingsRepository.java"}.
2020

2121
### Kafka
2222

2323
We use ES/CQRS to materialize the events into the state. Kafka acts as a broker and we use Spring Kafka.
2424

25-
Check :fileLink[RatingsListener]{path="workshop/src/main/java/com/example/demo/streams/RatingsListener.java"}.
25+
Check :fileLink[RatingsListener]{path="src/main/java/com/example/demo/streams/RatingsListener.java"}.
2626

2727
## API
2828

29-
The API is a Spring Web REST controller :fileLink[RatingsController]{path="workshop/src/main/java/com/example/demo/api/RatingsController.java"} and exposes two endpoints:
29+
The API is a Spring Web REST controller :fileLink[RatingsController]{path="src/main/java/com/example/demo/api/RatingsController.java"} and exposes two endpoints:
3030

3131
* `POST /ratings { "talkId": ?, "value": 1-5 }` to add a rating for a talk
3232
* `GET /ratings?talkId=?` to get the histogram of ratings of the given talk

labspace/03-adding-some-tests.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ Add `AbstractIntegrationTest` class to `src/test/java` sourceset.
99

1010
It will be an abstract class with standard Spring Boot's testing framework annotations on it:
1111

12-
```java save-as=workshop/src/test/java/com/example/demo/AbstractIntegrationTest.java
12+
```java save-as=src/test/java/com/example/demo/AbstractIntegrationTest.java
1313
package com.example.demo;
1414

1515
import org.springframework.boot.test.context.SpringBootTest;
@@ -25,7 +25,7 @@ public class AbstractIntegrationTest {
2525
Now we need to test that the context starts.
2626
Add `DemoApplicationTest`, extend it from your base class `AbstractIntegrationTest` and add a dummy test:
2727

28-
```java save-as=workshop/src/test/java/com/example/demo/DemoApplicationTest.java
28+
```java save-as=src/test/java/com/example/demo/DemoApplicationTest.java
2929
package com.example.demo;
3030

3131
import org.junit.jupiter.api.Test;
@@ -53,7 +53,7 @@ The Spring context starts. However, we need to populate the database with some d
5353

5454
Let's add a `schema.sql` file with the following content:
5555

56-
```sql save-as=workshop/src/test/resources/schema.sql
56+
```sql save-as=src/test/resources/schema.sql
5757
CREATE TABLE IF NOT EXISTS talks(
5858
id VARCHAR(64) NOT NULL,
5959
title VARCHAR(255) NOT NULL,
@@ -79,7 +79,7 @@ Now run the test again.
7979

8080
Oh no, it fails!
8181

82-
```plaintext
82+
```plaintext no-copy-button
8383
...
8484
Caused by: org.h2.jdbc.JdbcSQLException: Syntax error in SQL statement "INSERT INTO TALKS (ID, TITLE) VALUES ('testcontainers-integration-testing', 'Modern Integration Testing with Testcontainers') ON[*] CONFLICT DO NOTHING";
8585
...

labspace/04-your-first-testcontainers-integration.md

Lines changed: 59 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -12,38 +12,63 @@ It means that starting to use Testcontainers in our project \(once we add a depe
1212
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, properties = {
1313
"spring.datasource.url=jdbc:tc:postgresql:16-alpine://testcontainers/workshop"
1414
})
15+
public class ...
1516
```
1617

17-
Let's apply it to the `AbstractIntegrationTest` class:
18+
1. Update the :fileLink[`AbstractIntegrationTest`]{path="src/test/java/com/example/demo/AbstractIntegrationTest.java"} class to add the `@SpringBootTest` annotation:
1819

19-
```java save-as=workshop/src/test/java/com/example/demo/AbstractIntegrationTest.java
20-
package com.example.demo;
20+
```java save-as=src/test/java/com/example/demo/AbstractIntegrationTest.java
21+
package com.example.demo;
2122

22-
import org.springframework.boot.test.context.SpringBootTest;
23+
import org.springframework.boot.test.context.SpringBootTest;
2324

24-
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, properties = {
25-
"spring.datasource.url=jdbc:tc:postgresql:16-alpine://testcontainers/workshop"
26-
})
27-
public class AbstractIntegrationTest {
25+
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, properties = {
26+
"spring.datasource.url=jdbc:tc:postgresql:16-alpine://testcontainers/workshop"
27+
})
28+
public class AbstractIntegrationTest {
2829

29-
}
30-
```
30+
}
31+
```
3132

32-
If we split the magical JDBC url, we see:
33+
If we split the magical JDBC url, we see:
34+
35+
* `jdbc:tc:` - this part says that we should use Testcontainers as a JDBC provider
36+
* `postgresql:16-alpine://` - we use a PostgreSQL database, and we select the correct PostgreSQL image from the Docker Hub as the image
37+
* `testcontainers/workshop` - the host name \(can be anything\) is `testcontainers` and the database name is `workshop`. Your choice!
38+
39+
2. Run the tests with the following command:
40+
41+
```bash
42+
./mvnw clean test
43+
```
44+
45+
When the tests run, you'll see log output indicating it is pulling the `postgresql:16-alpine` container image. It will then use this container to run the tests.
46+
47+
Eventually, you will see output similar to the following indicating the tests ran successfully:
48+
49+
```console no-copy-button no-run-button
50+
[INFO] Results:
51+
[INFO]
52+
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
53+
[INFO]
54+
[INFO] ------------------------------------------------------------------------
55+
[INFO] BUILD SUCCESS
56+
[INFO] ------------------------------------------------------------------------
57+
[INFO] Total time: 15.529 s
58+
[INFO] Finished at: 2026-03-20T19:47:25Z
59+
[INFO] ------------------------------------------------------------------------
60+
```
3361
34-
* `jdbc:tc:` - this part says that we should use Testcontainers as JDBC provider
35-
* `postgresql:16-alpine://` - we use a PostgreSQL database, and we select the correct PostgreSQL image from the Docker Hub as the image
36-
* `testcontainers/workshop` - the host name \(can be anything\) is `testcontainers` and the database name is `workshop`. Your choice!
3762
38-
## Running tests
63+
## Running tests with Testcontainers Cloud
3964
40-
In order to run these test we'll need a Docker Engine available. To relax the system, we'll use Testcontainers Cloud to spin up Testcontainers-based containers.
65+
When the tests ran in the previous step, they used the Docker Engine on your machine. However, there are times in which you might not be able to easily run containers (such as in CI pipelines).
4166
42-
To enbale it you need to:
67+
In these environments, Testcontainers Cloud can be used to spin up Testcontainers-based containers. To enable it you need to:
4368
44-
1. Go to the [app.testcontainers.cloud](https://app.testcontainers.cloud/) and generate the TC_CLOUD_TOKEN.
69+
1. Go to the [app.testcontainers.cloud](https://app.testcontainers.cloud/) and generate the `TC_CLOUD_TOKEN`.
4570
46-
2. Set the TC_CLOUD_TOKEN as environment variable:
71+
2. Set the `TC_CLOUD_TOKEN` as environment variable:
4772
4873
::variableDefinition[tcc_token]{prompt="What is your TC_CLOUD_TOKEN value?"}
4974
@@ -65,26 +90,26 @@ To enbale it you need to:
6590
6691
Test is green? Good!
6792
68-
5. Check the logs.
93+
5. Check the logs to see the output indicating Testcontainers Cloud is being used:
6994
7095
```text
71-
2025-10-27T21:36:55.945Z INFO 77211 --- [ main] o.t.d.DockerClientProviderStrategy : Found Docker environment with Testcontainers Host with tc.host=tcp://127.0.0.1:43387
72-
2025-10-27T21:36:55.946Z INFO 77211 --- [ main] org.testcontainers.DockerClientFactory : Docker host IP address is 127.0.0.1
73-
2025-10-27T21:36:56.055Z INFO 77211 --- [ main] org.testcontainers.DockerClientFactory : Connected to docker:
96+
2026-03-20T21:36:55.945Z INFO 77211 --- [ main] o.t.d.DockerClientProviderStrategy : Found Docker environment with Testcontainers Host with tc.host=tcp://127.0.0.1:43387
97+
2026-03-20T21:36:55.946Z INFO 77211 --- [ main] org.testcontainers.DockerClientFactory : Docker host IP address is 127.0.0.1
98+
2026-03-20T21:36:56.055Z INFO 77211 --- [ main] org.testcontainers.DockerClientFactory : Connected to docker:
7499
Server Version: 28.3.3 (via Testcontainers Cloud Agent 1.22.0)
75100
API Version: 1.51
76101
Operating System: Ubuntu 22.04.5 LTS
77102
Total Memory: 31556 MB
78-
2025-10-27T21:36:56.206Z INFO 77211 --- [ main] tc.testcontainers/ryuk:0.8.1 : Creating container for image: testcontainers/ryuk:0.8.1
79-
2025-10-27T21:36:56.396Z INFO 77211 --- [ main] tc.testcontainers/ryuk:0.8.1 : Container testcontainers/ryuk:0.8.1 is starting: 779608b4dc49f2c37420ea0a39cc90951912fb767d7d7141c1b0ae1db1717989
80-
2025-10-27T21:36:57.321Z INFO 77211 --- [ main] tc.testcontainers/ryuk:0.8.1 : Container testcontainers/ryuk:0.8.1 started in PT1.114889292S
81-
2025-10-27T21:36:57.521Z INFO 77211 --- [ main] o.t.utility.RyukResourceReaper : Ryuk started - will monitor and terminate Testcontainers containers on JVM exit
82-
2025-10-27T21:36:57.523Z INFO 77211 --- [ main] org.testcontainers.DockerClientFactory : Checking the system...
83-
2025-10-27T21:36:57.530Z INFO 77211 --- [ main] org.testcontainers.DockerClientFactory : ✔︎ Docker server version should be at least 1.6.0
84-
2025-10-27T21:36:57.532Z INFO 77211 --- [ main] tc.postgres:17-alpine : Creating container for image: postgres:17-alpine
85-
2025-10-27T21:36:57.679Z INFO 77211 --- [ main] tc.postgres:17-alpine : Container postgres:17-alpine is starting: ed1a75d921ab911896763cde925724777aa6cea00700aec567d6b9a293b1e297
86-
2025-10-27T21:36:58.939Z INFO 77211 --- [ main] tc.postgres:17-alpine : Container postgres:17-alpine started in PT1.406803125S
87-
2025-10-27T21:36:58.943Z INFO 77211 --- [ main] tc.postgres:17-alpine : Container is started (JDBC URL: jdbc:postgresql://127.0.0.1:32771/workshop?loggerLevel=OFF)
103+
2026-03-20T21:36:56.206Z INFO 77211 --- [ main] tc.testcontainers/ryuk:0.8.1 : Creating container for image: testcontainers/ryuk:0.8.1
104+
2026-03-20T21:36:56.396Z INFO 77211 --- [ main] tc.testcontainers/ryuk:0.8.1 : Container testcontainers/ryuk:0.8.1 is starting: 779608b4dc49f2c37420ea0a39cc90951912fb767d7d7141c1b0ae1db1717989
105+
2026-03-20T21:36:57.321Z INFO 77211 --- [ main] tc.testcontainers/ryuk:0.8.1 : Container testcontainers/ryuk:0.8.1 started in PT1.114889292S
106+
2026-03-20T21:36:57.521Z INFO 77211 --- [ main] o.t.utility.RyukResourceReaper : Ryuk started - will monitor and terminate Testcontainers containers on JVM exit
107+
2026-03-20T21:36:57.523Z INFO 77211 --- [ main] org.testcontainers.DockerClientFactory : Checking the system...
108+
2026-03-20T21:36:57.530Z INFO 77211 --- [ main] org.testcontainers.DockerClientFactory : ✔︎ Docker server version should be at least 1.6.0
109+
2026-03-20T21:36:57.532Z INFO 77211 --- [ main] tc.postgres:17-alpine : Creating container for image: postgres:17-alpine
110+
2026-03-20T21:36:57.679Z INFO 77211 --- [ main] tc.postgres:17-alpine : Container postgres:17-alpine is starting: ed1a75d921ab911896763cde925724777aa6cea00700aec567d6b9a293b1e297
111+
2026-03-20T21:36:58.939Z INFO 77211 --- [ main] tc.postgres:17-alpine : Container postgres:17-alpine started in PT1.406803125S
112+
2026-03-20T21:36:58.943Z INFO 77211 --- [ main] tc.postgres:17-alpine : Container is started (JDBC URL: jdbc:postgresql://127.0.0.1:32771/workshop?loggerLevel=OFF)
88113
```
89114
90115
As you can see, Testcontainers quickly discovered your environment and connected to Docker.
@@ -96,7 +121,7 @@ It also performed some pre-flight checks to ensure the environment is valid.
96121
Changing the PostgreSQL version is as simple as replacing `16-alpine` with, for example, `17-alpine`.
97122
Try it, but don't forget that it will download the new image from the internet, if it's not already present on your computer.
98123

99-
```java save-as=workshop/src/test/java/com/example/demo/AbstractIntegrationTest.java
124+
```java save-as=src/test/java/com/example/demo/AbstractIntegrationTest.java
100125
package com.example.demo;
101126

102127
import org.springframework.boot.test.context.SpringBootTest;

labspace/05-dude-r-u-200-ok.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,17 @@
33
One of the great features of Spring Boot is the Actuator and its health endpoint.
44
It gives you an overview how healthy your app is.
55

6-
The context starts, but what's about the health of the app?
6+
The context starts, but what about the health of the app? This endpoint can also be used in a test to quickly validate the application starts up completely.
77

88
## Configure Rest Assured
99

1010
To check the health endpoint of our app, we will use the [RestAssured](http://rest-assured.io/) library.
1111

1212
However, before using it, we first need to configure it.
13-
Update your abstract test class with `setUpAbstractIntegrationTest` method since we will share it between all tests:
1413

15-
```java save-as=workshop/src/test/java/com/example/demo/AbstractIntegrationTest.java
14+
1. Update your abstract test class with `setUpAbstractIntegrationTest` method since we will share it between all tests:
15+
16+
```java save-as=src/test/java/com/example/demo/AbstractIntegrationTest.java
1617
package com.example.demo;
1718

1819
import io.restassured.RestAssured;
@@ -55,7 +56,7 @@ Now let's check if the app is actually healthy.
5556

5657
1. Add the `healthy` test implementation in the `DemoApplicationTest` class:
5758

58-
```java save-as=workshop/src/test/java/com/example/demo/DemoApplicationTest.java
59+
```java save-as=src/test/java/com/example/demo/DemoApplicationTest.java
5960
package com.example.demo;
6061

6162
import io.restassured.filter.log.LogDetail;
@@ -81,7 +82,7 @@ Now let's check if the app is actually healthy.
8182
./mvnw clean test
8283
```
8384

84-
Oh ow! it fails:
85+
Oh no! It fails:
8586

8687
```text
8788
...

labspace/06-adding-redis.md

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,23 +8,27 @@ The integration between the tests code and Testcontainers is straightforward.
88
Testcontainers comes with first class support for JUnit, but in our app we want to have a single Redis instance shared between **all** tests.
99
Luckily, there are the `.start()`/`.stop()` methods of `GenericContainer` to start or stop it manually.
1010

11-
Update the `AbstractIntegrationTest` with the following code:
11+
1. Update the :fileLink[`AbstractIntegrationTest`]{path="src/test/java/com/example/demo/AbstractIntegrationTest.java"} to create a redis container:
1212

13-
```java no-run-button
14-
static final GenericContainer redis = new GenericContainer("redis:7-alpine")
15-
.withExposedPorts(6379);
13+
```java no-run-button
14+
static final GenericContainer redis = new GenericContainer("redis:7-alpine")
15+
.withExposedPorts(6379);
16+
```
1617

17-
@DynamicPropertySource
18-
public static void configureRedis(DynamicPropertyRegistry registry) {
19-
redis.start();
20-
registry.add("spring.data.redis.host", redis::getHost);
21-
registry.add("spring.data.redis.port", redis::getFirstMappedPort);
22-
}
23-
```
18+
2. In that same class, add a method annotated with `@DynamicPropertySource` to provide the properties needed to connect to the Redis container:
19+
20+
```java
21+
@DynamicPropertySource
22+
public static void configureRedis(DynamicPropertyRegistry registry) {
23+
redis.start();
24+
registry.add("spring.data.redis.host", redis::getHost);
25+
registry.add("spring.data.redis.port", redis::getFirstMappedPort);
26+
}
27+
```
2428

25-
The full `AbstractIntegrationTest` class implementation will look like:
29+
The full `AbstractIntegrationTest` class implementation should now look like the following:
2630

27-
```java save-as=workshop/src/test/java/com/example/demo/AbstractIntegrationTest.java
31+
```java save-as=src/test/java/com/example/demo/AbstractIntegrationTest.java
2832
package com.example.demo;
2933

3034
import io.restassured.RestAssured;
@@ -78,4 +82,21 @@ Run the tests, now they should all pass.
7882

7983
```bash
8084
./mvnw clean test
85+
```
86+
87+
If successful, you should see output similar to the following:
88+
89+
```console no-copy-button no-run-button
90+
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 6.688 s - in com.example.demo.DemoApplicationTest
91+
[INFO]
92+
[INFO] Results:
93+
[INFO]
94+
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
95+
[INFO]
96+
[INFO] ------------------------------------------------------------------------
97+
[INFO] BUILD SUCCESS
98+
[INFO] ------------------------------------------------------------------------
99+
[INFO] Total time: 8.092 s
100+
[INFO] Finished at: 2026-03-20T19:59:19Z
101+
[INFO] ------------------------------------------------------------------------
81102
```

labspace/07-test-the-api.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
Now let's create a test for our API which will verify the business logic.
44

5-
```java save-as=workshop/src/test/java/com/example/demo/RatingsControllerTest.java
5+
```java save-as=src/test/java/com/example/demo/RatingsControllerTest.java
66
package com.example.demo;
77

88
import com.example.demo.model.Rating;
@@ -86,7 +86,7 @@ There is a [Testcontainers Kafka module](https://java.testcontainers.org/modules
8686
Just add it the same way as you added Redis and set the `spring.kafka.bootstrap-servers` system property.
8787
The full `AbstractIntegrationTest` class implementation will look like:
8888

89-
```java save-as=workshop/src/test/java/com/example/demo/AbstractIntegrationTest.java
89+
```java save-as=src/test/java/com/example/demo/AbstractIntegrationTest.java
9090
package com.example.demo;
9191

9292
import io.restassured.RestAssured;

0 commit comments

Comments
 (0)