Skip to content

Commit 1884ace

Browse files
committed
docs: Add more details in README.md file🫸🌀✏️📗🐧🐳
1 parent 4a49799 commit 1884ace

File tree

5 files changed

+375
-1
lines changed

5 files changed

+375
-1
lines changed

README.md

Lines changed: 215 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,215 @@
1-
# spring-boot-test-container-postgresql
1+
# Spring Boot Test Container PostgreSQL
2+
3+
A demonstration project showcasing the integration of Spring Boot with Testcontainers for PostgreSQL database testing.
4+
5+
## Overview
6+
7+
This project is a RESTful API for managing blog posts, built with Spring Boot and PostgreSQL. It demonstrates how to use
8+
Testcontainers for integration testing with a real PostgreSQL database.
9+
10+
## Technologies Used
11+
12+
- Java 21 (with preview features)
13+
- Spring Boot 3.5.0
14+
- Spring Data JDBC
15+
- Spring WebFlux (WebClient)
16+
- PostgreSQL
17+
- Testcontainers
18+
- Docker & Docker Compose
19+
- Maven
20+
- Springdoc OpenAPI
21+
22+
## Prerequisites
23+
24+
- Java 21 or higher
25+
- Docker and Docker Compose
26+
- Maven
27+
28+
## Getting Started
29+
30+
### Clone the Repository
31+
32+
```bash
33+
git clone https://github.com/hendisantika/spring-boot-test-container-postgresql.git
34+
cd spring-boot-test-container-postgresql
35+
```
36+
37+
### Build and Run
38+
39+
You can run the application using Maven:
40+
41+
```bash
42+
./mvnw spring-boot:run
43+
```
44+
45+
Or build and run the JAR file:
46+
47+
```bash
48+
./mvnw clean package
49+
java -jar target/test-container-postgresql-0.0.1-SNAPSHOT.jar
50+
```
51+
52+
### Using Docker Compose
53+
54+
The project includes a `compose.yaml` file for running the PostgreSQL database:
55+
56+
```bash
57+
docker-compose up -d
58+
```
59+
60+
## API Endpoints
61+
62+
The application provides the following RESTful API endpoints:
63+
64+
- `GET /api/posts` - Get all posts
65+
- `GET /api/posts/{id}` - Get a post by ID
66+
- `POST /api/posts` - Create a new post
67+
- `PUT /api/posts/{id}` - Update an existing post
68+
- `DELETE /api/posts/{id}` - Delete a post
69+
70+
### API Documentation
71+
72+
The API documentation is available via Springdoc OpenAPI at:
73+
74+
```
75+
http://localhost:8080/swagger-ui.html
76+
```
77+
78+
### Using cURL
79+
80+
You can interact with the API using cURL commands:
81+
82+
#### Get All Posts
83+
84+
```bash
85+
curl -X GET http://localhost:8080/api/posts
86+
```
87+
88+
#### Get Post by ID
89+
90+
```bash
91+
curl -X GET http://localhost:8080/api/posts/1
92+
```
93+
94+
#### Create a New Post
95+
96+
```bash
97+
curl -X POST http://localhost:8080/api/posts \
98+
-H "Content-Type: application/json" \
99+
-d '{"userId": 1, "title": "New Post", "body": "This is a new post created with cURL"}'
100+
```
101+
102+
#### Update a Post
103+
104+
```bash
105+
curl -X PUT http://localhost:8080/api/posts/1 \
106+
-H "Content-Type: application/json" \
107+
-d '{"userId": 1, "title": "Updated Post", "body": "This post was updated with cURL"}'
108+
```
109+
110+
#### Delete a Post
111+
112+
```bash
113+
curl -X DELETE http://localhost:8080/api/posts/1
114+
```
115+
116+
### HTTP Client
117+
118+
The project includes a WebClient-based HTTP client for programmatic API access. The client is implemented in the
119+
`PostClient` class.
120+
121+
#### Using the HTTP Client
122+
123+
To use the HTTP client in your code:
124+
125+
```java
126+
127+
@Autowired
128+
private PostClient postClient;
129+
130+
// Get all posts
131+
Flux<Post> posts = postClient.getAllPosts();
132+
133+
// Get post by ID
134+
Mono<Post> post = postClient.getPostById(1);
135+
136+
// Create a new post
137+
Post newPost = new Post(null, 1, "New Post", "This is a new post", null);
138+
Mono<Post> createdPost = postClient.createPost(newPost);
139+
140+
// Update a post
141+
Post updatedPost = new Post(1, 1, "Updated Post", "This post was updated", null);
142+
Mono<Post> result = postClient.updatePost(1, updatedPost);
143+
144+
// Delete a post
145+
Mono<Void> deleteResult = postClient.deletePost(1);
146+
```
147+
148+
#### Running the Example Client
149+
150+
The project includes an example client that demonstrates how to use the HTTP client. To run it:
151+
152+
```bash
153+
./mvnw spring-boot:run -Dspring-boot.run.profiles=client-example
154+
```
155+
156+
## Data Model
157+
158+
The application uses a simple `Post` model with the following fields:
159+
160+
- `id` (Integer) - Primary key
161+
- `userId` (Integer) - User ID associated with the post
162+
- `title` (String) - Post title (required)
163+
- `body` (String) - Post content (required)
164+
- `version` (Integer) - Version for optimistic locking
165+
166+
## Testing
167+
168+
The project demonstrates several testing approaches using Testcontainers:
169+
170+
### Repository Tests
171+
172+
Tests the repository layer with a real PostgreSQL database using Testcontainers.
173+
174+
```bash
175+
./mvnw test -Dtest=PostRepositoryTest
176+
```
177+
178+
### Controller Tests
179+
180+
Tests the controller layer with a real PostgreSQL database using Testcontainers.
181+
182+
```bash
183+
./mvnw test -Dtest=PostControllerTest
184+
```
185+
186+
### Integration Tests
187+
188+
Tests the entire application with a real PostgreSQL database using Testcontainers.
189+
190+
```bash
191+
./mvnw test -Dtest=SpringBootTestContainerPostgresqlApplicationTests
192+
```
193+
194+
## Project Structure
195+
196+
- `src/main/java` - Java source code
197+
- `controller` - REST controllers
198+
- `model` - Data models
199+
- `repository` - Data access layer
200+
- `exception` - Custom exceptions
201+
- `src/main/resources` - Application resources
202+
- `data/posts.json` - Initial data for the application
203+
- `schema.sql` - Database schema definition
204+
- `src/test/java` - Test source code
205+
206+
## License
207+
208+
This project is open source and available under the [MIT License](LICENSE).
209+
210+
## Author
211+
212+
Hendi Santika
213+
214+
215+
- Telegram: @hendisantika34

pom.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,10 @@
8585
<artifactId>spring-boot-starter-test</artifactId>
8686
<scope>test</scope>
8787
</dependency>
88+
<dependency>
89+
<groupId>org.springframework.boot</groupId>
90+
<artifactId>spring-boot-starter-webflux</artifactId>
91+
</dependency>
8892
</dependencies>
8993

9094
<build>
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package id.my.hendisantika.testcontainerpostgresql.client;
2+
3+
import id.my.hendisantika.testcontainerpostgresql.model.Post;
4+
import org.springframework.stereotype.Component;
5+
import org.springframework.web.reactive.function.client.WebClient;
6+
import reactor.core.publisher.Flux;
7+
import reactor.core.publisher.Mono;
8+
9+
/**
10+
* Created by IntelliJ IDEA.
11+
* Project : spring-boot-test-container-postgresql
12+
* User: hendisantika
13+
14+
* Telegram : @hendisantika34
15+
* Date: 18/11/24
16+
* Time: 08.30
17+
* To change this template use File | Settings | File Templates.
18+
*/
19+
@Component
20+
public class PostClient {
21+
22+
private final WebClient webClient;
23+
24+
public PostClient(WebClient.Builder webClientBuilder) {
25+
this.webClient = webClientBuilder.baseUrl("http://localhost:8080").build();
26+
}
27+
28+
public Flux<Post> getAllPosts() {
29+
return webClient.get()
30+
.uri("/api/posts")
31+
.retrieve()
32+
.bodyToFlux(Post.class);
33+
}
34+
35+
public Mono<Post> getPostById(Integer id) {
36+
return webClient.get()
37+
.uri("/api/posts/{id}", id)
38+
.retrieve()
39+
.bodyToMono(Post.class);
40+
}
41+
42+
public Mono<Post> createPost(Post post) {
43+
return webClient.post()
44+
.uri("/api/posts")
45+
.bodyValue(post)
46+
.retrieve()
47+
.bodyToMono(Post.class);
48+
}
49+
50+
public Mono<Post> updatePost(Integer id, Post post) {
51+
return webClient.put()
52+
.uri("/api/posts/{id}", id)
53+
.bodyValue(post)
54+
.retrieve()
55+
.bodyToMono(Post.class);
56+
}
57+
58+
public Mono<Void> deletePost(Integer id) {
59+
return webClient.delete()
60+
.uri("/api/posts/{id}", id)
61+
.retrieve()
62+
.bodyToMono(Void.class);
63+
}
64+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package id.my.hendisantika.testcontainerpostgresql.client;
2+
3+
import id.my.hendisantika.testcontainerpostgresql.model.Post;
4+
import org.springframework.boot.CommandLineRunner;
5+
import org.springframework.context.annotation.Profile;
6+
import org.springframework.stereotype.Component;
7+
8+
/**
9+
* Created by IntelliJ IDEA.
10+
* Project : spring-boot-test-container-postgresql
11+
* User: hendisantika
12+
13+
* Telegram : @hendisantika34
14+
* Date: 18/11/24
15+
* Time: 08.40
16+
* To change this template use File | Settings | File Templates.
17+
* <p>
18+
* This class demonstrates how to use the PostClient.
19+
* It's disabled by default (only runs with "client-example" profile).
20+
* To run it, use: ./mvnw spring-boot:run -Dspring-boot.run.profiles=client-example
21+
*/
22+
@Component
23+
@Profile("client-example")
24+
public class PostClientExample implements CommandLineRunner {
25+
26+
private final PostClient postClient;
27+
28+
public PostClientExample(PostClient postClient) {
29+
this.postClient = postClient;
30+
}
31+
32+
@Override
33+
public void run(String... args) {
34+
System.out.println("Running PostClient example...");
35+
36+
// Get all posts
37+
postClient.getAllPosts()
38+
.collectList()
39+
.doOnNext(posts -> System.out.println("Found " + posts.size() + " posts"))
40+
.block();
41+
42+
// Get post by ID
43+
Post post = postClient.getPostById(1)
44+
.doOnNext(p -> System.out.println("Found post: " + p))
45+
.block();
46+
47+
// Create a new post
48+
if (post != null) {
49+
Post newPost = new Post(null, post.userId(), "New Post Title", "New Post Body", null);
50+
postClient.createPost(newPost)
51+
.doOnNext(p -> System.out.println("Created post: " + p))
52+
.block();
53+
}
54+
55+
// Example of how to update a post
56+
if (post != null) {
57+
Post updatedPost = new Post(post.id(), post.userId(), "Updated Title", "Updated Body", post.version());
58+
postClient.updatePost(post.id(), updatedPost)
59+
.doOnNext(p -> System.out.println("Updated post: " + p))
60+
.block();
61+
}
62+
63+
// Example of how to delete a post (commented out to prevent actual deletion)
64+
// postClient.deletePost(101).block();
65+
66+
System.out.println("PostClient example completed.");
67+
}
68+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package id.my.hendisantika.testcontainerpostgresql.config;
2+
3+
import org.springframework.context.annotation.Bean;
4+
import org.springframework.context.annotation.Configuration;
5+
import org.springframework.web.reactive.function.client.WebClient;
6+
7+
/**
8+
* Created by IntelliJ IDEA.
9+
* Project : spring-boot-test-container-postgresql
10+
* User: hendisantika
11+
12+
* Telegram : @hendisantika34
13+
* Date: 18/11/24
14+
* Time: 08.35
15+
* To change this template use File | Settings | File Templates.
16+
*/
17+
@Configuration
18+
public class WebClientConfig {
19+
20+
@Bean
21+
public WebClient.Builder webClientBuilder() {
22+
return WebClient.builder();
23+
}
24+
}

0 commit comments

Comments
 (0)