Skip to content

Commit cf3c696

Browse files
author
Jonathan Henrique Medeiros
committed
feature: integration tests example with extensions
1 parent 9541aef commit cf3c696

13 files changed

+389
-29
lines changed

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<parent>
66
<groupId>org.springframework.boot</groupId>
77
<artifactId>spring-boot-starter-parent</artifactId>
8-
<version>3.3.0</version>
8+
<version>3.3.1</version>
99
<relativePath/>
1010
</parent>
1111

src/main/java/br/com/multidatasources/model/Billionaire.java

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,22 @@
44
import jakarta.persistence.Column;
55
import jakarta.persistence.Entity;
66
import jakarta.persistence.Table;
7+
import jakarta.persistence.UniqueConstraint;
78

89
import java.util.Objects;
910

10-
@Entity
11-
@Table(name = "billionaire")
11+
@Entity(name = "Billionaire")
12+
@Table(
13+
name = "billionaires",
14+
uniqueConstraints = {
15+
@UniqueConstraint(
16+
name = "uk_billionaires_idempotency_id",
17+
columnNames = {
18+
"idempotency_id"
19+
}
20+
)
21+
}
22+
)
1223
public class Billionaire extends IdempotentEntity {
1324

1425
@Column(name = "first_name", nullable = false)

src/main/resources/db/migration/V0001__inialize-database-schema.sql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
CREATE TABLE billionaire (
1+
CREATE TABLE billionaires (
22
id INT AUTO_INCREMENT PRIMARY KEY,
33
first_name VARCHAR(255) NOT NULL,
44
last_name VARCHAR(255) NOT NULL,
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
SET foreign_key_checks = 0;
22

3-
TRUNCATE TABLE billionaire;
3+
TRUNCATE TABLE billionaires;
44

55
SET foreign_key_checks = 1;
66

7-
INSERT INTO billionaire (first_name, last_name, career, idempotency_id) VALUES ('Aliko', 'Dangote', 'Billionaire Industrialist', UUID()),
8-
('Bill', 'Gates', 'Billionaire Tech Entrepreneur', UUID()),
9-
('Folrunsho', 'Alakija', 'Billionaire Oil Magnate', UUID());
7+
INSERT INTO billionaires (first_name, last_name, career, idempotency_id) VALUES ('Aliko', 'Dangote', 'Billionaire Industrialist', UUID()),
8+
('Bill', 'Gates', 'Billionaire Tech Entrepreneur', UUID()),
9+
('Folrunsho', 'Alakija', 'Billionaire Oil Magnate', UUID());
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package br.com.multidatasources;
2+
3+
import org.junit.jupiter.api.extension.BeforeEachCallback;
4+
import org.junit.jupiter.api.extension.ExtensionContext;
5+
import org.springframework.data.jpa.repository.JpaRepository;
6+
import org.springframework.data.repository.CrudRepository;
7+
import org.springframework.stereotype.Repository;
8+
import org.springframework.test.context.ActiveProfiles;
9+
import org.springframework.test.context.junit.jupiter.SpringExtension;
10+
11+
import java.util.Map;
12+
13+
@ActiveProfiles("integration-test")
14+
public class CleanupDatabaseExtension implements BeforeEachCallback {
15+
16+
@Override
17+
public void beforeEach(final ExtensionContext context) {
18+
final var applicationContext = SpringExtension.getApplicationContext(context);
19+
final var repositoryBeans = applicationContext.getBeansWithAnnotation(Repository.class);
20+
cleanupDatabase(repositoryBeans);
21+
}
22+
23+
private static void cleanupDatabase(final Map<String, Object> repositoryBeans) {
24+
repositoryBeans.values().forEach(CleanupDatabaseExtension::deleteAll);
25+
}
26+
27+
private static void deleteAll(final Object repository) {
28+
switch (repository) {
29+
case JpaRepository<?, ?> jpaRepository -> jpaRepository.deleteAllInBatch();
30+
case CrudRepository<?, ?> crudRepository -> crudRepository.deleteAll();
31+
default -> throw new IllegalArgumentException("Unsupported repository type: " + repository.getClass());
32+
}
33+
}
34+
35+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package br.com.multidatasources;
2+
3+
import br.com.multidatasources.config.datasource.DataSourceRoutingConfiguration;
4+
import br.com.multidatasources.config.datasource.master.MasterDataSourceConfiguration;
5+
import br.com.multidatasources.config.datasource.replica.ReplicaDataSourceConfiguration;
6+
import br.com.multidatasources.config.properties.datasource.DataSourceConnectionPropertiesConfiguration;
7+
import org.junit.jupiter.api.extension.ExtendWith;
8+
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
9+
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
10+
import org.springframework.context.annotation.Import;
11+
import org.springframework.test.context.ActiveProfiles;
12+
13+
import java.lang.annotation.ElementType;
14+
import java.lang.annotation.Inherited;
15+
import java.lang.annotation.Retention;
16+
import java.lang.annotation.RetentionPolicy;
17+
import java.lang.annotation.Target;
18+
19+
@Target(ElementType.TYPE)
20+
@Retention(RetentionPolicy.RUNTIME)
21+
@Inherited
22+
@ActiveProfiles("integration-test")
23+
@DataJpaTest
24+
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
25+
@ExtendWith(CleanupDatabaseExtension.class)
26+
@Import(
27+
value = {
28+
MasterDataSourceConfiguration.class,
29+
ReplicaDataSourceConfiguration.class,
30+
DataSourceRoutingConfiguration.class,
31+
DataSourceConnectionPropertiesConfiguration.class
32+
}
33+
)
34+
public @interface DatabaseRepositoryIntegrationTest {
35+
36+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package br.com.multidatasources;
2+
3+
import java.lang.annotation.ElementType;
4+
import java.lang.annotation.Retention;
5+
import java.lang.annotation.RetentionPolicy;
6+
import java.lang.annotation.Target;
7+
import java.util.UUID;
8+
9+
@Retention(RetentionPolicy.RUNTIME)
10+
@Target(ElementType.PARAMETER)
11+
public @interface DefaultBillionaire {
12+
13+
String firstName() default "John";
14+
15+
String lastName() default "Doe";
16+
17+
String career() default "SWE";
18+
19+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package br.com.multidatasources;
2+
3+
import br.com.multidatasources.model.Billionaire;
4+
import br.com.multidatasources.model.factory.BillionaireBuilder;
5+
import org.junit.jupiter.api.extension.ExtensionContext;
6+
import org.junit.jupiter.api.extension.ParameterContext;
7+
import org.junit.jupiter.api.extension.ParameterResolutionException;
8+
import org.junit.jupiter.api.extension.ParameterResolver;
9+
10+
import java.lang.reflect.Parameter;
11+
12+
public class DefaultBillionaireParameterResolverExtension implements ParameterResolver {
13+
14+
@Override
15+
public boolean supportsParameter(final ParameterContext parameterContext, final ExtensionContext extensionContext) throws ParameterResolutionException {
16+
return parameterContext.isAnnotated(DefaultBillionaire.class);
17+
}
18+
19+
@Override
20+
public Object resolveParameter(final ParameterContext parameterContext, final ExtensionContext extensionContext) throws ParameterResolutionException {
21+
return defaultBillionaire(parameterContext.getParameter());
22+
}
23+
24+
private static Billionaire defaultBillionaire(final Parameter parameter) {
25+
if (parameter.getType() != Billionaire.class) {
26+
return null;
27+
}
28+
29+
final var firstName = parameter.getAnnotation(DefaultBillionaire.class).firstName();
30+
final var lastName = parameter.getAnnotation(DefaultBillionaire.class).lastName();
31+
final var career = parameter.getAnnotation(DefaultBillionaire.class).career();
32+
33+
return BillionaireBuilder.builder()
34+
.firstName(firstName)
35+
.lastName(lastName)
36+
.career(career)
37+
.build();
38+
}
39+
40+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package br.com.multidatasources;
2+
3+
import org.junit.jupiter.api.extension.ExtendWith;
4+
import org.springframework.boot.test.context.SpringBootTest;
5+
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
6+
import org.springframework.core.annotation.AliasFor;
7+
import org.springframework.test.context.ActiveProfiles;
8+
9+
import java.lang.annotation.ElementType;
10+
import java.lang.annotation.Inherited;
11+
import java.lang.annotation.Retention;
12+
import java.lang.annotation.RetentionPolicy;
13+
import java.lang.annotation.Target;
14+
15+
@Target(ElementType.TYPE)
16+
@Retention(RetentionPolicy.RUNTIME)
17+
@Inherited
18+
@ActiveProfiles("integration-test")
19+
@SpringBootTest(
20+
webEnvironment = WebEnvironment.NONE
21+
)
22+
@ExtendWith(CleanupDatabaseExtension.class)
23+
public @interface IntegrationTest {
24+
25+
@AliasFor(annotation = SpringBootTest.class, attribute = "classes")
26+
Class<?>[] classes() default {};
27+
28+
@AliasFor(annotation = SpringBootTest.class, attribute = "properties")
29+
String[] properties() default {};
30+
31+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package br.com.multidatasources.repository;
2+
3+
import br.com.multidatasources.DatabaseRepositoryIntegrationTest;
4+
import br.com.multidatasources.model.factory.BillionaireBuilder;
5+
import br.com.multidatasources.service.v1.idempotency.impl.UUIDIdempotencyGenerator;
6+
import org.apache.commons.lang3.RandomStringUtils;
7+
import org.junit.jupiter.api.Test;
8+
import org.springframework.beans.factory.annotation.Autowired;
9+
10+
import java.util.UUID;
11+
import java.util.stream.IntStream;
12+
13+
import static org.assertj.core.api.Assertions.assertThat;
14+
15+
@DatabaseRepositoryIntegrationTest
16+
class BillionaireRepositoryIntegrationTest {
17+
18+
@Autowired
19+
private BillionaireRepository billionaireRepository;
20+
21+
@Test
22+
void givenExistentIdempotencyId_whenExistsBillionaireByIdempotencyId_thenReturnTrue() {
23+
final var idempotencyGenerator = new UUIDIdempotencyGenerator();
24+
final var billionaire = BillionaireBuilder.builder()
25+
.firstName("John")
26+
.lastName("Doe")
27+
.career("SWE")
28+
.build();
29+
30+
billionaire.generateIdempotencyId(idempotencyGenerator);
31+
this.billionaireRepository.save(billionaire);
32+
33+
final var actual = this.billionaireRepository.existsBillionaireByIdempotencyId(billionaire.getIdempotencyId());
34+
35+
assertThat(actual).isTrue();
36+
}
37+
38+
@Test
39+
void givenANonExistentIdempotencyId_whenExistsBillionaireByIdempotencyId_thenReturnFalse() {
40+
final var actual = this.billionaireRepository.existsBillionaireByIdempotencyId(UUID.randomUUID());
41+
assertThat(actual).isFalse();
42+
}
43+
44+
@Test
45+
void validateDeleteAllByCallbackExtension() {
46+
// Should be empty table on database
47+
assertThat(this.billionaireRepository.count()).isZero();
48+
49+
final var idempotencyGenerator = new UUIDIdempotencyGenerator();
50+
51+
// Generate 10 billionaires with random data
52+
final var billionaires = IntStream.range(1, 11)
53+
.mapToObj(index -> {
54+
final var billionaire = BillionaireBuilder.builder()
55+
.firstName(RandomStringUtils.randomAlphabetic(index))
56+
.lastName(RandomStringUtils.randomAlphabetic(index))
57+
.career(RandomStringUtils.randomAlphabetic(index))
58+
.build();
59+
billionaire.generateIdempotencyId(idempotencyGenerator);
60+
return billionaire;
61+
}).toList();
62+
63+
// Save all billionaires
64+
this.billionaireRepository.saveAllAndFlush(billionaires);
65+
66+
// Should have 10 billionaires on database
67+
assertThat(this.billionaireRepository.count()).isEqualTo(10);
68+
}
69+
70+
}

0 commit comments

Comments
 (0)