17 Haziran 2020 Çarşamba

SpringBoot Test @DataJpaTest Anotasyonu

Giriş
Şu satırı dahil ederiz
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
Not : Bu anotasyon yerine gerçek veri tabanında test için @AutoConfigureTestDatabase tercih edilebilir.

Kullanım Amacı
1. Repository sınıflarının test edilmesi
2. Service sınıflarının test edilmesi

@DataJpaTest Anotasyonu Ne Yapar?
@WebMvcTest Anotasyonu gibi sadece bazı bean'leri yükler. Açıklaması şöyle. Dolayısıyla @SpringBootTest anotasyonunu kullanmaya gerek kalmaz.
The @DataJpaTest annotation will only provide the autoconfiguration required to test Spring Data JPA using an in-memory database such as H2.

This annotation is used instead of @SpringBootTest.
Bu anotasyonla bellekte bir veri tabanı oluşturulur. Açıklaması şöyle
By default, tests annotated with @DataJpaTest will use an embedded in-memory database (replacing any explicit or usually auto-configured DataSource).
Eğer embedded bir veri tabanı bulamazsa şöyle bir exception fırlatır
java.lang.IllegalStateException : Failed to replace DataSource with an embedded database for tests. If you want an embedded database please put a supported one the classpath or tune the replace attribute of @AutoConfigurationTestDatabase
Veri tabanının Oluşturulması
Test Suite çalışınca bir kere bellekte yeni bir veri tabanı oluşturulur. Bunun çıktısı olarak Spring logusu görülebilir. Daha sonra her  test sadece en başta yaratılmış olan bellekteki veri tabanına CRUD işlemi yapar. 

Transaction
Açıklaması şöyle
By default, @DataJpaTest tests are transactional and roll back at the end of each test.
Örnek
Rollback işlemi olmasın diye @Commit kullanılabilir. Şöyle yaparız
import org.springframework.test.annotation.Commit;

@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
@DataJpaTest(showSql = true)
class FooRepositoryTest {

  @Autowired
  private FooRepository repository;

  @Test
  @Order(1)
  @Commit
  void autoGenerateId() {

    FooEntity entity = new FooEntity();
    entity.setInstant(Instant.ofEpochMilli(0));
    repository.save(entity);
    assertEquals(entity.getId(), 1);

  }

  @Test
  @Order(2)
  void findById() {

    List<FooEntity> all = repository.findAll();
    assertFalse(all.isEmpty());
    FooEntity entity = all.get(0);
    assertEquals(entity.getInstant(), Instant.ofEpochMilli(0));
  }
}
Repository Sınıflarının Test Edilmesi

1. TestEntityManager Inject Edilmesini Sağlar
Açıklaması şöyle.
Using this annotation we can have a TestEntityManager injected on the test, and use it to change the database to a state in which it's possible to unit test our repository methods.
Test kodu içinde TestEntityManager @AutoWire edilebilir.

2. Repository Sınıflarının Inject Edilmesini Sağlar
Örnek
Şöyle yaparız
@DataJpaTest
class PersonRepositoryDataJpaTest {

  @Autowired
  private PersonRepository personRepository;

  @Test
  void shouldReturnAllLastNames() {
    personRepository.saveAndFlush(new Person().setFirstName("John").setLastName("Brown"));
    personRepository.saveAndFlush(new Person().setFirstName("Kyle").setLastName("Green"));
    personRepository.saveAndFlush(new Person().setFirstName("Paul").setLastName("Brown"));

    assertEquals(Set.of("Brown", "Green"), personRepository.findAllLastNames());
  }
}
Örnek
Şöyle yaparız. Burada respository kullanılarak kaydedilen nesneye ID atandığı test ediliyor.
@DataJpaTest
class WebAuthnUserRepositoryTest {

  @Autowired
  WebAuthnUserRepository userRepository;

  @Test
  public void test() {
    WebAuthnUser user = new WebAuthnUser();
    user.setUsername("junit");
    userRepository.save(user);

    assertNotNull(user.getId());
  }
}
Örnek
Tam çalışan bir örnek şöyle
@DataJpaTest
@ExtendWith(SpringExtension.class)
@AutoConfigureTestDatabase(connection = EmbeddedDatabaseConnection.H2)
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class OrderRepositoryUnitTest {

  @Autowired
  OrderRepository orderRepository;

  @BeforeEach
  public void setUp() {
    orderRepository.save(new Order(100L, "jane", 200.0, 2));
    orderRepository.save(new Order(200L, "ben", 100.0, 5));
  }

  @AfterEach
  public void destroy() {
    orderRepository.deleteAll();
  }

  @Test
  public void testGetAllOrders() {
    List<Order> orderList = orderRepository.findAll();
    Assertions.assertThat(orderList.size()).isEqualTo(2);
    Assertions.assertThat(orderList.get(0).getId()).isNotNegative();
    Assertions.assertThat(orderList.get(0).getId()).isGreaterThan(0);
    Assertions.assertThat(orderList.get(0).getBuyer()).isEqualTo("jane");
  }

  @Test
  public void testGetInvalidOrder() {
    Exception exception = assertThrows(NoSuchElementException.class, () -> {
        orderRepository.findById(120L).get();
    });
    Assertions.assertThat(exception).isNotNull();
    Assertions.assertThat(exception.getClass()).isEqualTo(NoSuchElementException.class);
    Assertions.assertThat(exception.getMessage()).isEqualTo("No value present");
  }

  @Test
  public void testGetCreateOrder() {
    Order saved = new Order(300L, "tim", 50.0, 4);
    Order returned = orderRepository.save(saved);
    Assertions.assertThat(returned).isNotNull();
    Assertions.assertThat(returned.getBuyer()).isNotEmpty();
    Assertions.assertThat(returned.getId()).isGreaterThan(1);
    Assertions.assertThat(returned.getId()).isNotNegative();
    Assertions.assertThat(saved.getBuyer()).isEqualTo(returned.getBuyer());
  }

  @Test
  public void testDeleteOrder() {
    Order saved = new Order(400L, "ron", 60.0, 3);
    orderRepository.save(saved);
    orderRepository.delete(saved);
    Exception exception = assertThrows(NoSuchElementException.class, () -> {
      orderRepository.findById(400L).get();
    });
    Assertions.assertThat(exception).isNotNull();
    Assertions.assertThat(exception.getClass()).isEqualTo(NoSuchElementException.class);
    Assertions.assertThat(exception.getMessage()).isEqualTo("No value present");
  }
}
Örnek
Şöyle yaparız. @ContextConfiguration bir test anotasyonu. Bu anotasyon ile test içinde yüklenecek @Configuration olarak işaretli sınıfları belirtiriz.
@RunWith( SpringRunner.class )
@DataJpaTest
@ContextConfiguration(classes=PersistenceConfig.class)
public class PersonRepositoryTest {

    // Tests ...
}
Service Sınıflarının Test Edilmesi - Kullanmayın
Açıklaması şöyle
... you can use @DataJpaTest annotation that starts JPA components and Repository beans. and use @Import annotation to load the service class itself.
Ancak bu kullanım şeklinde dikkatli olmak lazım, çünkü tüm test tek bir transaction ile çalışıyor. Açıklaması şöyle
@DataJpaTest wraps the suite with the @Transactional. So, the test suite and the service are both transactional. The default propagation level for the annotation is REQUIRED. It means that calling another transactional method does not start a new transaction. Instead, it continues to execute SQL statements in the current one.
Örnek
Şöyle yaparız. Burada hem Service sınıfının yüklenmesi sağlanıyor, hem de @AutoConfigureTestDatabase ile gerçek veri tabanı kullanılıyor
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase.Replace
.ANY
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest
import org.springframework.context.annotation.ComponentScan
import org.springframework.context.annotation.FilterType.ASSIGNABLE_TYPE

@DataJpaTest(
    includeFilters = [
        ComponentScan.Filter(ViewersService::class, type = ASSIGNABLE_TYPE)
    ]
)
@AutoConfigureTestDatabase(replace = ANY)
internal class ViewersServiceIntegrationTest {
  @Autowired
  private lateinit var viewersService: ViewersService

  @Autowired
  private lateinit var viewersRepository: ViewersRepository
  ... 
}
Örnek
Şöyle yaparız
@DataJpaTest(showSql = false)
@Import(TestService.class)
public class ServiceTest {
  @Autowired
  private TestService service;
  
  @Test
  void testFindAll() {
    List<String> values = service.findAll();
    assertEquals(1, values.size());
  }
}
Örnek
Test koşmaya başlayınca otomatik Transaction'ı başlatmamak için şöyle yaparız
@DataJpaTest
@Transactional(propagation = Propagation.NOT_SUPPORTED)
class PersonCreateServiceImplTestH2 {

  @Autowired
  private PersonRepository personRepository;
  @MockBean
  private PersonValidateService personValidateService;
  @Autowired
  private PersonCreateService personCreateService;

  @BeforeEach
  void beforeEach() {
    personRepository.deleteAll();
  }

  @TestConfiguration
  static class Config {

    @Bean
    public PersonCreateService personCreateService(
        PersonRepository personRepository,
        PersonValidateService personValidateService
    ) {
      return new PersonCreateServiceImpl(personValidateService, personRepository);
    }
  }
  // test cases...
}
Dolayısıyla artık bu test servis kodu exception fırlatınca geçmeye başlar.
@Test
void shouldRollbackIfAnyUserIsNotValidated() {
  doThrow(new ValidationFailedException(""))
    .when(personValidateService)
    .checkUserCreation("John", "Brown");
  
  assertThrows(ValidationFailedException.class, () -> personCreateService.createFamily(
        List.of("Matilda", "Vasya", "John"),
        "Brown"
    ));
  assertEquals(0, personRepository.count());
}
@DataJpaTest Anotasyonu Alternatifi
Örnek
Hem tüm projeyi yüklemek, hem de H2 veri tabanı yaratmak için şöyle yaparız@AutoConfigureTestDatabase otomatik olarak bir veri tabanı yaratır
@SpringBootTest(webEnvironment = WebEnvironment.NONE)
@AutoConfigureTestDatabase
class PersonCreateServiceImplSpringBootTest {

  @Autowired
  private PersonRepository personRepository;
  @MockBean
  private PersonValidateService personValidateService;
  @Autowired
  private PersonCreateService personCreateService;

  @BeforeEach
  void init() {
    personRepository.deleteAll();
  }
}

Hiç yorum yok:

Yorum Gönder