Skip to content

Commit 3a7ff60

Browse files
integration tests
1 parent 7ecc61a commit 3a7ff60

File tree

8 files changed

+304
-5
lines changed

8 files changed

+304
-5
lines changed

pom.xml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,11 @@
8181
<groupId>org.springframework.boot</groupId>
8282
<artifactId>spring-boot-testcontainers</artifactId>
8383
</dependency>
84+
<dependency>
85+
<groupId>org.springframework.boot</groupId>
86+
<artifactId>spring-boot-starter-webflux</artifactId>
87+
<scope>test</scope>
88+
</dependency>
8489
</dependencies>
8590

8691
<build>
@@ -116,6 +121,26 @@
116121
</to>
117122
</configuration>
118123
</plugin>
124+
<plugin>
125+
<groupId>org.apache.maven.plugins</groupId>
126+
<artifactId>maven-surefire-plugin</artifactId>
127+
<configuration>
128+
<excludes>
129+
<exclude>**/*IntegrationTest.java</exclude>
130+
<exclude>**/*IT.java</exclude>
131+
</excludes>
132+
</configuration>
133+
</plugin>
134+
<plugin>
135+
<groupId>org.apache.maven.plugins</groupId>
136+
<artifactId>maven-failsafe-plugin</artifactId>
137+
<configuration>
138+
<includes>
139+
<include>**/*IntegrationTest.java</include>
140+
<include>**/*IT.java</include>
141+
</includes>
142+
</configuration>
143+
</plugin>
119144
</plugins>
120145
</build>
121146

src/main/java/com/amigoscode/Main.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ public static void main(String[] args) {
1717
SpringApplication.run(Main.class, args);
1818
}
1919

20-
@Bean
20+
// @Bean
2121
public CommandLineRunner commandLineRunner(
2222
ProductRepository productRepository) {
2323
return args -> {

src/main/java/com/amigoscode/product/ProductController.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package com.amigoscode.product;
22

33
import jakarta.validation.Valid;
4-
import org.springframework.validation.annotation.Validated;
4+
import org.springframework.http.HttpStatus;
55
import org.springframework.web.bind.annotation.*;
66

77
import java.util.List;
@@ -33,6 +33,7 @@ public void deleteProductById(@PathVariable("id") UUID id) {
3333
}
3434

3535
@PostMapping
36+
@ResponseStatus(HttpStatus.CREATED)
3637
public UUID saveProduct(@RequestBody @Valid NewProductRequest product) {
3738
return productService.saveNewProduct(product);
3839
}

src/main/java/com/amigoscode/product/ProductResponse.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ public record ProductResponse(
1111
BigDecimal price,
1212
String imageUrl,
1313
Integer stockLevel,
14-
Instant createdAt,
14+
boolean isPublished, Instant createdAt,
1515
Instant updatedAt,
1616
Instant deletedAt
1717
) {

src/main/java/com/amigoscode/product/ProductService.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ Function<Product, ProductResponse> mapToResponse() {
6262
p.getPrice(),
6363
p.getImageUrl(),
6464
p.getStockLevel(),
65+
p.getPublished(),
6566
p.getCreatedAt(),
6667
p.getUpdatedAt(),
6768
p.getDeletedAt()

src/main/resources/application.properties

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
spring.application.name=jfs
2-
spring.datasource.url=jdbc:postgresql://localhost:5432/jfs
2+
spring.datasource.url=jdbc:postgresql://localhost:5333/jfs
33
spring.datasource.username=amigoscode
4-
spring.datasource.password=
4+
spring.datasource.password=password
55
spring.datasource.driver-class-name=org.postgresql.Driver
66

77
spring.jpa.hibernate.ddl-auto=validate
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package com.amigoscode;
2+
3+
import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient;
4+
import org.springframework.boot.test.context.SpringBootTest;
5+
import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
6+
import org.springframework.test.annotation.DirtiesContext;
7+
import org.testcontainers.junit.jupiter.Container;
8+
import org.testcontainers.junit.jupiter.Testcontainers;
9+
10+
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
11+
@AutoConfigureWebTestClient
12+
@Testcontainers
13+
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS)
14+
public abstract class AbstractTestConfig {
15+
16+
@Container
17+
@ServiceConnection
18+
private static final SharedPostgresContainer POSTGRES =
19+
SharedPostgresContainer.getInstance();
20+
}
Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
package com.amigoscode.journey;
2+
3+
import com.amigoscode.AbstractTestConfig;
4+
import com.amigoscode.product.NewProductRequest;
5+
import com.amigoscode.product.ProductResponse;
6+
import com.amigoscode.product.UpdateProductRequest;
7+
import org.junit.jupiter.api.Test;
8+
import org.springframework.beans.factory.annotation.Autowired;
9+
import org.springframework.core.ParameterizedTypeReference;
10+
import org.springframework.http.MediaType;
11+
import org.springframework.test.web.reactive.server.WebTestClient;
12+
13+
import java.math.BigDecimal;
14+
import java.util.List;
15+
import java.util.UUID;
16+
17+
import static org.assertj.core.api.Assertions.assertThat;
18+
19+
public class ProductIT extends AbstractTestConfig {
20+
21+
public static final String PRODUCT_BASE_URL = "api/v1/products";
22+
@Autowired
23+
private WebTestClient webTestClient;
24+
25+
@Test
26+
void canCreateProduct() {
27+
createProduct(new NewProductRequest(
28+
"Laptop",
29+
"1gb ram etc",
30+
BigDecimal.TEN,
31+
100,
32+
"https://amigoscode.com/laptop.png"
33+
));
34+
}
35+
36+
private UUID createProduct(NewProductRequest request) {
37+
// when
38+
return webTestClient
39+
.post()
40+
.uri(PRODUCT_BASE_URL)
41+
.bodyValue(request)
42+
.exchange()
43+
.expectStatus()
44+
.isCreated()
45+
.expectBody(UUID.class)
46+
.value(uuid -> assertThat(uuid).isNotNull())
47+
.returnResult()
48+
.getResponseBody();
49+
}
50+
51+
@Test
52+
void canGetAllProducts() {
53+
// given 1st product
54+
NewProductRequest laptop = new NewProductRequest(
55+
"Laptop",
56+
"1gb ram etc",
57+
new BigDecimal("10.00"),
58+
100,
59+
"https://amigoscode.com/laptop.png"
60+
);
61+
var laptopId = createProduct(laptop);
62+
63+
// given 2nd product
64+
NewProductRequest tv = new NewProductRequest(
65+
"Tv",
66+
"LED with crystal clear nano...",
67+
new BigDecimal("1.00"),
68+
50,
69+
"https://amigoscode.com/tv.png"
70+
);
71+
var tvId = createProduct(tv);
72+
73+
// when
74+
List<ProductResponse> products = webTestClient.get()
75+
.uri(PRODUCT_BASE_URL)
76+
.accept(MediaType.APPLICATION_JSON)
77+
.exchange()
78+
.expectStatus()
79+
.isOk()
80+
.expectBodyList(new ParameterizedTypeReference<ProductResponse>() {
81+
})
82+
.returnResult()
83+
.getResponseBody();
84+
// then
85+
var productsFromDb = products.stream()
86+
.filter(
87+
p -> p.id().equals(tvId) || p.id().equals(laptopId)
88+
)
89+
.toList();
90+
91+
assertThat(productsFromDb)
92+
.hasSize(2)
93+
.usingRecursiveFieldByFieldElementComparatorIgnoringFields(
94+
"createdAt", "updatedAt", "deletedAt"
95+
)
96+
.containsExactlyInAnyOrder(
97+
new ProductResponse(
98+
tvId,
99+
tv.name(),
100+
tv.description(),
101+
tv.price(),
102+
tv.imageUrl(),
103+
tv.stockLevel(),
104+
true, null, null, null
105+
),
106+
new ProductResponse(
107+
laptopId,
108+
laptop.name(),
109+
laptop.description(),
110+
laptop.price(),
111+
laptop.imageUrl(),
112+
laptop.stockLevel(),
113+
true, null, null, null
114+
)
115+
);
116+
}
117+
118+
@Test
119+
void canGetProductById() {
120+
// given
121+
NewProductRequest laptop = new NewProductRequest(
122+
"Laptop",
123+
"1gb ram etc",
124+
new BigDecimal("10.00"),
125+
100,
126+
"https://amigoscode.com/laptop.png"
127+
);
128+
var laptopId = createProduct(laptop);
129+
130+
// when
131+
ProductResponse responseBody = webTestClient.get()
132+
.uri(PRODUCT_BASE_URL + "/" + laptopId)
133+
.accept(MediaType.APPLICATION_JSON)
134+
.exchange()
135+
.expectStatus()
136+
.isOk()
137+
.expectBody(new ParameterizedTypeReference<ProductResponse>() {
138+
})
139+
.returnResult()
140+
.getResponseBody();
141+
142+
// then
143+
assertThat(responseBody)
144+
.usingRecursiveComparison()
145+
.ignoringFields("createdAt", "updatedAt", "deletedAt")
146+
.isEqualTo(
147+
new ProductResponse(
148+
laptopId,
149+
laptop.name(),
150+
laptop.description(),
151+
laptop.price(),
152+
laptop.imageUrl(),
153+
laptop.stockLevel(),
154+
true, null, null, null
155+
)
156+
);
157+
}
158+
159+
@Test
160+
void canGetDeleteProductById() {
161+
// given
162+
NewProductRequest laptop = new NewProductRequest(
163+
"Laptop",
164+
"1gb ram etc",
165+
new BigDecimal("10.00"),
166+
100,
167+
"https://amigoscode.com/laptop.png"
168+
);
169+
var laptopId = createProduct(laptop);
170+
171+
// when
172+
webTestClient.delete()
173+
.uri(PRODUCT_BASE_URL + "/" + laptopId)
174+
.accept(MediaType.APPLICATION_JSON)
175+
.exchange()
176+
.expectStatus()
177+
.isOk()
178+
.expectBody()
179+
.isEmpty();
180+
181+
// then
182+
webTestClient.get()
183+
.uri(PRODUCT_BASE_URL + "/" + laptopId)
184+
.accept(MediaType.APPLICATION_JSON)
185+
.exchange()
186+
.expectStatus()
187+
.isNotFound()
188+
.expectBody()
189+
.jsonPath("$.message").isEqualTo("product with id [" + laptopId +"] not found")
190+
.jsonPath("$.error").isEqualTo("Not Found")
191+
.jsonPath("$.statusCode").isEqualTo("404")
192+
.jsonPath("$.path").isEqualTo("/api/v1/products/" + laptopId)
193+
.jsonPath("$.fieldError").doesNotExist();
194+
195+
// {"message":"product with id [02682eec-6d8e-43ac-a5c8-60dd7bb6fa14] not found","error":"Not Found","statusCode":404,"path":"/api/v1/products/02682eec-6d8e-43ac-a5c8-60dd7bb6fa14","timestamp":"2025-06-26T16:00:01.854366Z","fieldErrors":null}
196+
197+
198+
}
199+
200+
@Test
201+
void canUpdateProduct() {
202+
// given
203+
NewProductRequest laptop = new NewProductRequest(
204+
"Laptop",
205+
"1gb ram etc",
206+
new BigDecimal("10.00"),
207+
100,
208+
"https://amigoscode.com/laptop.png"
209+
);
210+
var laptopId = createProduct(laptop);
211+
212+
// when
213+
webTestClient.put()
214+
.uri(PRODUCT_BASE_URL + "/" + laptopId)
215+
.accept(MediaType.APPLICATION_JSON)
216+
.bodyValue(new UpdateProductRequest(
217+
null, null, null, new BigDecimal("200.00"), 500, false
218+
))
219+
.exchange()
220+
.expectStatus()
221+
.isOk()
222+
.expectBody()
223+
.isEmpty();
224+
// then
225+
ProductResponse responseBody = webTestClient.get()
226+
.uri(PRODUCT_BASE_URL + "/" + laptopId)
227+
.accept(MediaType.APPLICATION_JSON)
228+
.exchange()
229+
.expectStatus()
230+
.isOk()
231+
.expectBody(new ParameterizedTypeReference<ProductResponse>() {
232+
})
233+
.returnResult()
234+
.getResponseBody();
235+
236+
// then
237+
assertThat(responseBody)
238+
.usingRecursiveComparison()
239+
.ignoringFields("createdAt", "updatedAt", "deletedAt")
240+
.isEqualTo(
241+
new ProductResponse(
242+
laptopId,
243+
laptop.name(),
244+
laptop.description(),
245+
new BigDecimal("200.00"),
246+
laptop.imageUrl(),
247+
500,
248+
false, null, null, null
249+
)
250+
);
251+
}
252+
}

0 commit comments

Comments
 (0)