21 Ağustos 2019 Çarşamba

SpringBoot Test @SpringBootTest Anotasyonu

Giriş
Şu satırı dahil ederiz
import org.springframework.boot.test.context.SpringBootTest;
Açıklaması şöyle
spring-boot-test-starter (in the test scope) contains Junit5、Mockito、 Spring Testlibraries and we find these common libraries to be useful when writing tests.
Spring ile integration test yapmayı sağlayan anotasyonlardan birisidir. Bu anotasyon unit test için değildir, çünkü eğer özel bir ayar yapmadıysak tüm application context'i ayağa kaldırır. Yani tüm uygulama çalışır. Eğer uygulamamızdaki bazı katmanları test etmek istiyorsak Test Slices başlığı altında geçen, ve daha kısıtlı bir application context yaratılmasını sağlayan diğer anotasyonları kullanmak gerekir.

Eğer amacımız integration test değilse
Eğer amacımız integration test değilse :
- Tüm application context yerine @ContextConfiguration anotasyonu ile teste kullanılacak bean'leri yaratabiliriz.
- @TestConfiguration anotasyonu ile testte kullanılacak bean'leri yaratabiliriz
- Basit JUnit kullanılabilir.
- Mockito ile mock kullanabiliriz

Mockito Kullanma
Örnek - JUnit5
Şöyle yaparız
@ExtendWith(MockitoExtension.class) public class ProductManagerTest { @InjectMocks private ProductManager productManager; @Mock private ProductDAO productDAO; @Test public void givenProduct_whenCreate_thenReturnProduct() { } }
Örnek - JUnit4
Elimizde şöyle bir kod olsun
@Service
public class Demo{
  @Autowired
  private Demo1 demo1;
  public Boolean doInvoke() {
    //do something
  }
}

@SpringBootTest(classes=Demo.class)
public class demoTest{

  @Autowired
  private Demo demo;
  @MockBean
  private Demo1 demo1;
  @Test
  public void test() {
    BDDMockito.given(demo1.test).willReturn(true);
    Boolean result = demo.doInvoke();
    Assertions.assertTrue(result);
  }
}
Burada @SpringBootTest kullanmaya gerek yok. Şöyle yaparız
@RunWith(MockitoJUnitRunner.class) public class DemoTest{ @Mock private Demo1 demo1; @InjectMocks private Demo demo; @Test public void test() { BDDMockito.given(demo1.test).willReturn(true); Boolean result = demo.doInvoke(); Assertions.assertTrue(result); } }
Eğer amacımız integration test ise
- Customization için @SpringBootTest anotasyonunun properties alanı kullanılabilir.
- @ActiveProfiles anotasyonu kullanılabilir. Böylece belirtilen application-<profile>.properties veya application-<profile>.yml dosyaları kullanılır
- @TestPropertySource anotasyonu kullanılabilir.
- @MockBean anotasyonu ile gerçek bean yerine mock bir bean kullanabiliriz
- @TestConfiguration anotasyonu ile uygulamadaki bean'lere ilave olarak yeni bir test bean'i yaratabiliriz ,veya bir bean'i override edebiliriz.
- @Configuration anotasyon ile teste mahsus bir primary configuration yaratabiliriz.
@SpringBootTest anotasyonunun classes alanı kullanılarak application context içinde hangi sınıfların olacağı belirtilir
- Integration test için yine tüm context'i kaldırmaya gerek olmayabilir. Bunun için SpringBoot Test Sliced Test Anotasyonları yazısına da bakabilirsiniz

@SpringBootTest Ne Yapar
Test sınıfının bulunduğu paketten başlayarak yukarı doğru çıkar ve @SpringBootConfiguration anotasyonunu arar. Bu anotasyonu ayrı olarak tanımlamadıysak @SpringBootApplication yani main sınıfımız bu anotasyonu sağlar.

ApplicationContext  Cache'lenmesi
Açıklaması şöyle
Starting a Spring context for your test takes a good amount of time. Especially if you populate the whole context using @SpringBootTest. This adds an additional overhead (time-wise) to your test execution and may drastically increase your total build time.

Fortunately, Spring Test provides a mechanism to cache an already started application context and reuse it for subsequent tests. You can think of a basic Map that stores different application contexts. Spring Test creates a uniquely identifiable key for the Map entry.
Açıklaması şöyle.
Spring Boot provides a @SpringBootTest annotation which can be used as an alternative to the standard spring-test @ContextConfiguration annotation when you need Spring Boot features. The annotation works by creating the ApplicationContext used in your tests via SpringApplication. The Spring TestContext framework stores application contexts in a static cache. This means that the context is literally stored in a static variable. In other words, if tests execute in separate processes the static cache will be cleared between each test execution, and this will effectively disable the caching mechanism. To benefit from the caching mechanism, all tests must run within the same process or test suite. This can be achieved by executing all tests as a group within an IDE
Açıklaması şöyle.
By default, once loaded, the configured ApplicationContext is reused for each test. Thus the setup cost is incurred only once per test suite, and subsequent test execution is much faster. In this context, the term test suite means all tests run in the same JVM
ApplicationContext  Cache'lenmesini Engelleyen Şeyler
Eğer testte @Autowired varsa sorun yok
Ama testlerdeki bazı anotasyonlar cache'i kullanmayı engelliyor. Bu da testlerin yavaş çalışmasına sebep olur. Açıklaması şöyle. Bunlardan en problemli olanlar @MockBean ve @SpyBean
By reading these articles and the same ones, I realized that there are some pitfalls in integration tests that prevent the Spring from reusing the loaded application context in integration tests. Here are the most important ones: 

- Using @MockBean and @SpyBean
- Using @DirtiesContext
- Careless use of profiles in integration tests
- Careless use of @TestPropertySource
MockMvc Inject Edilmesi
Eğer gerçek bir web sunucusu kullanmıyorsak, tüm application context'i ayağa kaldırıdıp MockMvc kullanmak mümkün. Burada @AutoConfigureMockMvc ile bir adet MockMvc autowire ediliyor.
Örnek
Şöyle yaparız
@ExtendWith(SpringExtension.class)
@SpringBootTest
@AutoConfigureMockMvc
class RegisterUseCaseIntegrationTest {

  @Autowired
  private MockMvc mockMvc;

  @Autowired
  private ObjectMapper objectMapper;

  @Autowired
  private UserRepository userRepository;

  @Test
  void registrationWorksThroughAllLayers() throws Exception {
    UserResource user = new UserResource("Zaphod", "zaphod@galaxy.net");

    mockMvc.perform(post("/forums/{forumId}/register", 42L)
            .contentType("application/json")
            .param("sendWelcomeMail", "true")
            .content(objectMapper.writeValueAsString(user)))
            .andExpect(status().isOk());

    UserEntity userEntity = userRepository.findByName("Zaphod");
    assertThat(userEntity.getEmail()).isEqualTo("zaphod@galaxy.net");
  }

}
TestRestTemplate Inject Edilmesi
TestRestTemplate yazısına taşıdım

classes Alanı
classes Alanı yazısına taşıdım

webEnvironment Alanı
Açıklaması şöyle. Eğer bu alanı tanımlarsak bir tane web sunucusu yaratılır.
By default, @SpringBootTest will not start a server. You can use the webEnvironment attribute of @SpringBootTest to further refine how your tests run:
Sunucuyu değişik modlarda başlatmak mümkün. Açıklaması şöyle.
This annotation configures a complete test environment with all your beans and everything set up. You can choose to start a mock servlet environment, or even start a real one with the webEnvironment attribute of the @SpringBootTest annotation.
Şu değerleri alabilir.
MOCK
RANDOM_PORT
DEFINED_PORT
NONE

DEFINED_PORT Değeri
Gerçek bir servlet ortamı başlatır.

RANDOM_PORT Değeri
Açıklaması şöyle. Gerçek bir servlet ortamı başlatır.
Loads a WebServerApplicationContext and provides a real web environment. Embedded servers are started and listen on a random port.
Örnek
Şöyle yaparız
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)

Hiç yorum yok:

Yorum Gönder