Skip to content

Commit

Permalink
Update Steps docs
Browse files Browse the repository at this point in the history
  • Loading branch information
sivaprasadreddy committed Nov 3, 2023
1 parent 296bfa1 commit cc01798
Show file tree
Hide file tree
Showing 11 changed files with 178 additions and 112 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/maven.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,4 @@ jobs:
cache: 'maven'

- name: Test
run: ./mvnw compile
run: ./mvnw test
7 changes: 5 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
HELP.md
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
Expand Down Expand Up @@ -30,4 +29,8 @@ build/
!**/src/test/**/build/

### VS Code ###
.vscode/
.vscode/

/src/test
!/src/test/java/com/testcontainers/catalog/.keep
!/src/test/resources/logback-test.xml
2 changes: 1 addition & 1 deletion .sdkmanrc
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
java=17.0.8-tem
maven=3.9.4
maven=3.9.5
7 changes: 3 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,13 @@

This workshop will explain how to use Testcontainers \([https://www.testcontainers.com](https://www.testcontainers.com)\) in your Java application development process.

We will work with a Spring Boot application and explore:
* Using Testcontainers for provisioning application dependent services like PostgreSQL, Kafka, LocalStack for local development.
We will work with a Spring Boot application and explore how to:
* Use Testcontainers for provisioning application dependent services like PostgreSQL, Kafka, LocalStack for local development
* Use [Testcontainers Desktop](https://testcontainers.com/desktop/) for local development and debugging
* Write tests using Testcontainers
* Using [Testcontainers Desktop](https://testcontainers.com/desktop/) for local development and debugging

## Table of contents

* [Introduction](README.md)
* [Step 1: Getting Started](step-1-getting-started.md)
* [Step 2: Exploring the app](step-2-exploring-the-app.md)
* [Step 3: Local Development Environment with Testcontainers](step-3-local-development-environment.md)
Expand Down
11 changes: 0 additions & 11 deletions src/test/java/com/testcontainers/catalog/ApplicationTests.java

This file was deleted.

30 changes: 23 additions & 7 deletions step-1-getting-started.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
# Step 1: Getting Started
Before getting started, let's make sure you have everything you need for this workshop.

## Check Java
## Prerequisites

### Install Java 17 or newer
You'll need Java 17 or newer for this workshop.
Testcontainers libraries are compatible with Java 8+, but this workshop uses a Spring Boot 3.x application which requires Java 17 or newer.

## Check Docker
I would recommend using [SDKMAN](https://sdkman.io/) to install Java on your machine.

## Install Docker

Make sure you have a Docker environment available on your machine.

Expand Down Expand Up @@ -35,14 +40,23 @@ Server: Docker Desktop 4.24.2 (124339)
```

## Install Testcontainers Desktop
Download the latest version of Testcontainers Desktop app from [https://testcontainers.com/desktop/](https://testcontainers.com/desktop/) and install it on your machine.
Once you start the Testcontainers Desktop application, it will automatically detect the container runtimes installed on your system (Docker Desktop, OrbStack, etc)
[Testcontainers Desktop](https://testcontainers.com/desktop/) is a companion app for the open source Testcontainers libraries
that makes local development and testing with real dependencies simple.

Download the latest version of Testcontainers Desktop app from [https://testcontainers.com/desktop/](https://testcontainers.com/desktop/)
and install it on your machine.

Once you start the Testcontainers Desktop application, it will automatically detect the container runtimes
installed on your system (Docker Desktop, OrbStack, etc)
and allows you to choose which container runtime you want to use by Testcontainers.

## Download the project

Clone the following project from GitHub to your computer:
[https://github.com/testcontainers/java-local-development-workshop](https://github.com/testcontainers/java-local-development-workshop)
Clone the [java-local-development-workshop](https://github.com/testcontainers/java-local-development-workshop) repository from GitHub to your computer:

```shell
git clone https://github.com/testcontainers/java-local-development-workshop.git
```

## Compile the project to download the dependencies

Expand All @@ -58,8 +72,10 @@ This might be helpful if the internet connection at the workshop venue is somewh
```shell
docker pull postgres:16-alpine
docker pull localstack/localstack:2.3
docker pull confluentinc/cp-kafka:7.5.0
docker pull wiremock/wiremock:3.2.0-alpine
docker pull confluentinc/cp-kafka:7.5.0
docker pull confluentinc/cp-schema-registry:7.5.0
docker pull confluentinc/cp-enterprise-control-center:7.5.0
```

###
Expand Down
20 changes: 11 additions & 9 deletions step-2-exploring-the-app.md
Original file line number Diff line number Diff line change
@@ -1,36 +1,38 @@
# Step 2: Exploring the app

The application we are going to work is a microservice based on Spring Boot for managing a catalog of products.
The application we are going to work on is a microservice based on Spring Boot for managing a catalog of products.
It provides APIs to save and retrieve the product information.

![Sample App Architecture](assets/tcd-workshop-app.png)

## Storage

### SQL database with the products
## SQL database with the products

When a product is created, we will store the product information in our database.

Our database of choice is PostgreSQL, accessed with Spring Data JPA.

Check `com.testcontainers.catalog.domain.internal.ProductRepository`.

### LocalStack
## LocalStack

We would like to store the product images in AWS S3 Object storage.
But we will use [LocalStack](https://localstack.cloud/) to emulate the AWS cloud environment locally during local development and testing with Spring Cloud AWS.
We will use [LocalStack](https://localstack.cloud/) to emulate the AWS cloud environment locally during local development and testing with [Spring Cloud AWS](https://awspring.io/).

Check `com.testcontainers.catalog.domain.internal.S3FileStorageService`.

### Kafka
## Kafka

When a product image is uploaded to AWS S3, we should publish an event to Kafka.
When a product image is uploaded to AWS S3, an event will be published to Kafka.
The kafka event listener will then consume the event and update the product information with the image URL.

Check `com.testcontainers.catalog.domain.internal.ProductEventPublisher`
and `com.testcontainers.catalog.events.ProductEventListener`.

## API
## External Service Integrations
Our application talks to `inventory-service` to fetch the product availability information.
We will use [WireMock](https://wiremock.org/) to mock the `inventory-service` during local development and testing.

## API Endpoints

The API is a Spring Web REST controller \(`com.testcontainers.catalog.api.ProductController`\) and exposes the following endpoints:

Expand Down
107 changes: 59 additions & 48 deletions step-3-local-development-environment.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Step 3: Local Development environment with Testcontainers
Our application uses PostgreSQL, Kafka, LocalStack.
Our application uses PostgreSQL, Kafka, and LocalStack.

Currently, if you run the `Application.java` from your IDE, you will see the following error:

Expand All @@ -23,9 +23,10 @@ Consider the following:
Process finished with exit code 0
```

To run the application locally, we need to provision these services.
To run the application locally, we need to have these services up and running.

Instead of installing these services on our local machine, or using Docker to run these services manually,
we will use [Spring Boot support for Testcontainers at Development Time](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#features.testing.testcontainers.at-development-time) to provision these services.
we will use [Spring Boot support for Testcontainers at Development Time](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#features.testing.testcontainers.at-development-time) to provision these services automatically.

> **NOTE**
>
Expand Down Expand Up @@ -63,7 +64,8 @@ First, make sure you have the following Testcontainers dependencies in your `pom
```

We will also use **RestAssured** for API testing and **Awaitility** for testing asynchronous processes.
So, add the following dependencies as well

So, add the following dependencies as well:

```xml
<dependency>
Expand All @@ -79,6 +81,7 @@ So, add the following dependencies as well
```

## Create ContainersConfig class under src/test/java
Let's create `ContainersConfig` class under `src/test/java` to configure the required containers.

```java
package com.testcontainers.catalog;
Expand Down Expand Up @@ -131,17 +134,17 @@ public class ContainersConfig {
```

Let's understand what this configuration class does:
* `@TestConfiguration` annotation indicates that this class should be used for Spring Boot test configuration.
* Spring Boot provides ServiceConnection support for JdbcConnectionDetails and KafkaConnectionDetails out-of-the-box.
So, we configured PostgreSQLContainer and KafkaContainer as beans with @ServiceConnection annotation.
This configuration will automatically start these containers and register the DataSource and Kafka connection properties automatically.
* However, Spring Cloud AWS doesn't provide ServiceConnection support out-of-the-box.
* `@TestConfiguration` annotation indicates that this configuration class defines the beans that can be used for Spring Boot tests.
* Spring Boot provides `ServiceConnection` support for `JdbcConnectionDetails` and `KafkaConnectionDetails` out-of-the-box.
So, we configured `PostgreSQLContainer` and `KafkaContainer` as beans with `@ServiceConnection` annotation.
This configuration will automatically start these containers and register the **DataSource** and **Kafka** connection properties automatically.
* Spring Cloud AWS doesn't provide ServiceConnection support out-of-the-box [yet](https://github.com/awspring/spring-cloud-aws/issues/793).
But there is support for [Contributing Dynamic Properties at Development Time](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#features.testing.testcontainers.at-development-time.dynamic-properties).
So, we configured LocalStackContainer as a bean and registered the Spring Cloud AWS configuration properties using DynamicPropertyRegistry.
* We also configured an ApplicationRunner bean to create the AWS resources like S3 bucket upon application startup.
So, we configured `LocalStackContainer` as a bean and registered the Spring Cloud AWS configuration properties using `DynamicPropertyRegistry`.
* We also configured an `ApplicationRunner` bean to create the AWS resources like S3 bucket upon application startup.

## Create TestApplication class under src/test/java
Next, let's create a TestApplication class to start the application with the Testcontainers configuration.
Next, let's create a `TestApplication` class under `src/test/java` to start the application with the Testcontainers configuration.

```java
package com.testcontainers.catalog;
Expand All @@ -151,20 +154,22 @@ import org.springframework.boot.SpringApplication;
public class TestApplication {

public static void main(String[] args) {
SpringApplication.from(Application::main)
SpringApplication
//note that we are using Application from src/main/java instead of TestApplication from src/test/java
.from(Application::main)
.with(ContainersConfig.class)
.run(args);
}
}
```

Run the TestApplication from our IDE and verify that the application starts successfully.
Run the `TestApplication` from our IDE and verify that the application starts successfully.

Now, you can invoke the APIs using curl or Postman.
Now, you can invoke the APIs using CURL or Postman or any of your favourite HTTP Client tools.

### Create a product
```shell
curl -v --location 'http://localhost:8080/api/products' \
curl -v -X "GET" 'http://localhost:8080/api/products' \
--header 'Content-Type: application/json' \
--data '{
"code": "P201",
Expand All @@ -184,7 +189,7 @@ You should get a response similar to the following:

### Upload Product Image
```shell
curl --location 'http://localhost:8080/api/products/P101/image' \
curl -X "POST" 'http://localhost:8080/api/products/P101/image' \
--form 'file=@"/Users/siva/work/product-p101.jpg"'
```

Expand All @@ -197,7 +202,7 @@ You should see a response similar to the following:
### Get a product by code

```shell
curl --location 'http://localhost:8080/api/products/P101'
curl -X "GET" 'http://localhost:8080/api/products/P101'
```

You should be able to see the response similar to the following:
Expand All @@ -214,7 +219,7 @@ You should be able to see the response similar to the following:
}
```

If you check the application logs, you should see the following logs:
If you check the application logs, you should see the following error in logs:

```shell
com.testcontainers.catalog.domain.internal.DefaultProductService - Error while calling inventory service
Expand All @@ -230,7 +235,8 @@ org.springframework.web.client.ResourceAccessException: I/O error on GET request
at java.base/java.util.Optional.map(Optional.java:260)
```
When we invoke the GET /api/products/{code} API, the application tries to call the inventory service to get the inventory details.
When we invoke the `GET /api/products/{code}` API endpoint,
the application tried to call the inventory service to get the inventory details.
As the inventory service is not running, we get the above error.
Let's use WireMock to mock the inventory service APIs for our local development and testing.
Expand All @@ -247,31 +253,6 @@ Add the following dependency to your `pom.xml`:
</dependency>
```
Next, update the ContainersConfig class to start the WireMock container as well.
```java
package com.testcontainers.catalog;
...
...
import org.wiremock.integrations.testcontainers.WireMockContainer;
@TestConfiguration(proxyBeanMethods = false)
public class ContainersConfig {
...
...
@Bean
WireMockContainer wiremockServer(DynamicPropertyRegistry registry) {
WireMockContainer wiremockServer = new WireMockContainer("wiremock/wiremock:3.2.0-alpine")
.withMappingFromResource("mocks-config.json");
registry.add("application.inventory-service-url", wiremockServer::getBaseUrl);
return wiremockServer;
}
}
```
Create `src/test/resources/mocks-config.json` to define Mock API behaviour.
```json
Expand Down Expand Up @@ -325,13 +306,41 @@ Create `src/test/resources/mocks-config.json` to define Mock API behaviour.
}
```
Now restart the TestApplication and invoke the GET /api/products/P101 API again.
Next, update the `ContainersConfig` class to configure the `WireMockContainer` as follows:
```java
package com.testcontainers.catalog;
...
...
import org.wiremock.integrations.testcontainers.WireMockContainer;
@TestConfiguration(proxyBeanMethods = false)
public class ContainersConfig {
...
...
@Bean
WireMockContainer wiremockServer(DynamicPropertyRegistry registry) {
WireMockContainer wiremockServer = new WireMockContainer("wiremock/wiremock:3.2.0-alpine")
.withMappingFromResource("mocks-config.json");
registry.add("application.inventory-service-url", wiremockServer::getBaseUrl);
return wiremockServer;
}
}
```
Once the WireMock server is started, we are registering the WireMock server URL as `application.inventory-service-url`.
So, when we make a call to `inventory-service` from our application, it will call the WireMock server instead.
Now restart the `TestApplication` and invoke the `GET /api/products/P101` API again.
```shell
curl --location 'http://localhost:8080/api/products/P101'
```
You should the response similar to the following:
You should see the response similar to the following:
```json
{
Expand All @@ -347,7 +356,7 @@ You should the response similar to the following:
And there should be no error in the console logs.
Try `curl --location 'http://localhost:8080/api/products/P103'`.
Try `curl -X "GET" 'http://localhost:8080/api/products/P103'`.
You should get the following response with `"available":false` because we mocked inventory-service such that the quantity for P103 to be 0.
```json
Expand All @@ -362,5 +371,7 @@ You should get the following response with `"available":false` because we mocked
}
```
Now we have the working local development environment with PostgreSQL, Kafka, LocalStack, and WireMock.
###
[Next](step-4-connect-to-services.md)
Loading

0 comments on commit cc01798

Please sign in to comment.