Testcontainers, TestRestTemplate ve JUnit5 ile Integration Test
Önceki yazılarımızda unit testler üzerinde durmuştuk bugün ise integration test yazmaya çalışacağız. Bunun için Testcontainers‘dan yararlanıyor olacağız.
Testcontainers’ın sağladığı bazı özellikler arasında:
- Docker Entegrasyonu: Testcontainers, Docker üzerinde çalışan konteynerleri yönetmek için kullanılır. Bu sayede, testlerinizi farklı Docker konteynerlerinde çalıştırabilirsiniz.
- Kolay Kullanım: Testcontainers, testlerinizi Java veya diğer dillerde yazarken kolayca entegre edebileceğiniz bir API sağlar.
- Çeşitli Konteyner Seçenekleri: Testcontainers, birçok farklı konteyner türünü destekler. Örneğin, PostgreSQL, MySQL, Redis gibi popüler veritabanlarının yanı sıra Elasticsearch, Kafka gibi araçları da destekler.
- Performans: Testcontainers, her test çalıştırıldığında konteynerlerin temizlenmesini ve yeniden başlatılmasını sağlar, böylece testlerinizin izole edilmesini ve tekrarlanabilir olmasını sağlar.
Testcontainers bizlere uygulama geliştirme esnasında kullandığımız araçları sunuyor. Aşağıda bazılarını ekledim, kendi sitesi üzerinden detaylı bilgiye ulaşabilirsiniz.
- Elasticsearch
- Kafka
- MySQL
- Neo4J
- RabbitMq
- Redis
Bir örnek üzerinden inceleyelim.
Gereksinimler;
- Docker
- Spring Boot:3.2.2
- Java11+
pom.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
<dependency> <groupId>org.testcontainers</groupId> <artifactId>testcontainers</artifactId> <version>1.19.4</version> <scope>test</scope> </dependency> <dependency> <groupId>org.testcontainers</groupId> <artifactId>postgresql</artifactId> <version>1.18.3</version> <scope>test</scope> </dependency> |
Aşağıdaki controller için entegrasyon testi yazmaya çalışacağız.
Controller
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
@RestController @RequiredArgsConstructor @RequestMapping(Apis.Auth.BASE_URL) public class AuthenticationController { private final AuthenticationService authenticationService; private final UserMapper userMapper; @PostMapping(Apis.Auth.REGISTER) public GenericResponse<UserDto> register(@RequestBody RegisterDto registerDto){ RegisterModel mappedRegisterModel = registerMapper.toRegisterModel(registerDto); User savedUser = authenticationService.register(mappedRegisterModel); return GenericResponse.<UserDto>builder() .success(true) .message(I18nConstants.USER_REGISTRATION_SUCCESS) .data(userMapper.fromUser(savedUser)) .build(); } } |
Service
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
@Service @RequiredArgsConstructor @Slf4j public class AuthenticationService { private final UserService userService; private final RoleService roleService; public User register(RegisterDto registerDto) { var existingUser = userService.getByEmail(registerDto.getEmail()); if(Objects.nonNull(existingUser)){ log.error("user already exists. email:{}", registerDto.getEmail()); throw new UsernameAlreadyExistsException(); } Role role = roleService.findByName(RoleEnum.USER.name()); User newUser = prepareUser(registerDto, List.of(role)); return userService.save(newUser); } private User prepareUser(RegisterDto registerDto, List<Role> roles) { return User.builder() .email(registerDto.getEmail()) .roles(roles) .isPremium(false) .isDeleted(false) .password(passwordEncoder.encode(registerDto.getPassword())) .build(); } } |
Integration Test
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.DynamicPropertyRegistry; import org.springframework.test.context.DynamicPropertySource; import org.testcontainers.containers.PostgreSQLContainer; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; import org.testcontainers.utility.DockerImageName; import org.testcontainers.utility.MountableFile; import static org.junit.jupiter.api.Assertions.assertTrue; @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) @Testcontainers public abstract class AbstractIntegrationTest { private static final String POSTGRESQL_IMAGE_NAME = "postgres"; @Container static PostgreSQLContainer<?> postgreSQLContainer = new PostgreSQLContainer<>(DockerImageName.parse(POSTGRESQL_IMAGE_NAME)) .withCopyFileToContainer( MountableFile.forClasspathResource( "init.sql"), "/docker-entrypoint-initdb.d/"); @DynamicPropertySource static void properties(DynamicPropertyRegistry registry) { registry.add("spring.datasource.url", postgreSQLContainer::getJdbcUrl); registry.add("spring.datasource.username", postgreSQLContainer::getUsername); registry.add("spring.datasource.password", postgreSQLContainer::getPassword); } @Test void verify() { assertTrue(postgreSQLContainer.isRunning()); } } |
Uygulamam PostgreSQL ile çalıştığı için PostgreSQLContainer çalıştırıyor olacağım. Başka bir uygulamaya ihtiyacım yok.
TestContainers ile yukarıdaki örnekte olduğu gibi isterseniz PostgreSQLContainer oluşturabilirsiniz.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
import com.fasterxml.jackson.databind.ObjectMapper; import com.mylink.core.payload.GenericResponse; import com.mylink.domain.dto.auth.RegisterDto; import com.mylink.domain.dto.user.UserDto; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; public class UserControllerTest extends AbstractIntegrationTest { @Autowired private TestRestTemplate testRestTemplate; @Test void should_create_user_successfully() { RegisterDto request = prepareRegisterDto(); ResponseEntity<GenericResponse> response = testRestTemplate.postForEntity("/auth/register", request, GenericResponse.class); assertEquals(HttpStatus.OK, response.getStatusCode()); assertNotNull(response); assertNotNull(response.getBody()); ObjectMapper mapper = new ObjectMapper(); UserDto userDto = mapper.convertValue(response.getBody().getData(), UserDto.class); assertEquals(userDto.getEmail(), request.getEmail()); } private RegisterDto prepareRegisterDto() { RegisterDto registerDto = new RegisterDto(); registerDto.setPassword("password"); return registerDto; } } |
Spring bizim için ApplicationContext’i ayağa kaldıracak ve uygulamaya istek attığımızda bütün Bean’lerimiz hazır olacak. Biz de TestRestTemplate ile istek atıp senaryomuzun başarılı sonuçlanıp/sonuçlanmadığını test edebilir halde olacağız.
NOT: Testleri çalıştırmadan önce Docker Desktop’ın çalıştığından emin olun.
Kaynak;
Faydalı olması dileğiyle.