JUnit ile Servis Katmanı Testi-2

Cem Dırman
3 min readOct 17, 2023

--

Bu yazıda bir önceki yazıda bahsettiğimiz konuları daha ileri seviyeye taşımaya çalışacağız. Bu yazıyla beraber ihtiyacımız olan çoğu konuyu ele almış olacağız. Dolayısıyla özellikle yeni başlayan arkadaşların önceki yazıları da okumasını tavsiye ediyorum.

Servis Katmanı

package emlakcepte.service;

import java.time.LocalDateTime;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;

//imports

@Service
public class RealtyService {

private static final int MAX_INDIVICUAL_REALTY_SIZE = 5;

@Autowired
private UserService userService;

@Autowired
private RealtyRepository realtyRepository;

@Autowired
private BannerServiceClient bannerServiceClient;

private Logger logger = Logger.getLogger(RealtyService.class.getName());

public Realty create(RealtyRequest realtyRequest) {

User foundUser = userService.getById(realtyRequest.getUserId())
.orElseThrow(() -> new UserNotFoundException("kullanıcı bulunamadı"));

if (UserType.INDIVIDUAL.equals(foundUser.getType())) {
validateIndividualRealtySize(foundUser);
}

Realty realty = convert(realtyRequest, foundUser);

Banner bannerRequest = new Banner(String.valueOf(realty.getNo()), 1, "123123", "banner açıklaması");

BannerResponse bannerResponse = bannerServiceClient.create(bannerRequest);

if (!HttpStatus.CREATED.equals(bannerResponse.getHttpStatus())) {
logger.log(Level.WARNING, "Banner kaydedilemedi!");
throw new RuntimeException("Banner kaydedilemedi!");
}

return realtyRepository.save(realty);

}

private void validateIndividualRealtySize(User foundUser) {

List<Realty> realtyList = realtyRepository.findAllByUserId(foundUser.getId());

if (MAX_INDIVICUAL_REALTY_SIZE < realtyList.size()) {

logger.log(Level.INFO, "Bireysel kullanıcı en fazla 5 ilan girebilir. userID : {0}", foundUser.getId());

throw new EmlakCepteException("indivual.user.realty.max.size");
}

}

private Realty convert(RealtyRequest realtyRequest, User foundUser) {
Realty realty = new Realty();
realty.setNo(realtyRequest.getNo());
realty.setCreateDate(LocalDateTime.now());
realty.setStatus(RealtyType.PASSIVE);
realty.setTitle(realtyRequest.getTitle());
realty.setProvince(realtyRequest.getProvince());
realty.setUser(foundUser);
return realty;
}

}

Servisin içerisinde farklı kurallar eklemeye çalıştım ki biraz test yazmamız farklılaşsın ve gündelik test ihtiyaçlarımızı bu yazıda daha iyi örneklendirebilelim.

Unit Test

İlk testimiz kodda gördüğümüz üzere bir kullanıcı bulamadığında beklediğimiz hatanın fırlatılması.

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.catchThrowable;
import static org.mockito.Mockito.times;

import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.springframework.test.context.junit.jupiter.SpringExtension;

@ExtendWith(SpringExtension.class)
class RealtyServiceTest {

@InjectMocks
private RealtyService realtyService;

@Mock
private UserService userService;

@Mock
private RealtyRepository realtyRepository;

@Mock
private BannerServiceClient bannerServiceClient;

@Test
void it_should_throw_user_found_exception_when_user_is_null() {
// given

// when

Throwable exception = catchThrowable(() -> realtyService.create(new RealtyRequest()));

// then
assertThat(exception).isInstanceOf(UserNotFoundException.class);

}
}

İkinci testimiz ise bireysel kullanıcının reality(gayrimenkul) yayınlama hakkının dolması durumu.

 @Test
void it_should_throw_emlakcepte_exception_when_indivual_user_has_max_realty_size() {

// given

Optional<User> user = getUser(1, UserType.INDIVIDUAL);

Mockito.when(userService.getById(1)).thenReturn(user);

Mockito.when(realtyRepository.findAllByUserId(1)).thenReturn(getRealtyList(6));

// when

Throwable exception = catchThrowable(() -> realtyService.create(getRealtyRequest(1)));

// then
assertThat(exception).isInstanceOf(EmlakCepteException.class);

assertThat(exception.getMessage()).isEqualTo("Bireysel kullanıcı en fazla 5 ilan girebilir");

verify(userService, times(1)).getById(Mockito.eq(1)); //direkt olarak 1 de diyebilirdik. örnek amaçlı ekledim

verifyNoMoreInteractions(userService);

verify(realtyRepository, times(1)).findAllByUserId(Mockito.anyInt());

Mockito.verify(realtyRepository, times(0)).save(Mockito.any(Realty.class));

verifyNoInteractions(bannerServiceClient);

}

private Optional<User> getUser(int id, UserType type) {
return Optional.of(new User(id, "test", "test@gmail.com", "hashPassword", type));
}

private List<Realty> getRealtyList() {
return List.of(getRealty(1), getRealty(2), getRealty(3), getRealty(4), getRealty(5));
}

private Realty getRealty(int id) {
return new Realty(id, "test ilan", LocalDateTime.now(), RealtyType.ACTIVE);
}

Üstteki test methodunu incelediğimizde tam yedi tane assertion mevcut. Çünkü test yazdığımızda yapılmasını ve ya yapılmamasını istediğimiz durumlardan emin olmalıyız. Servis methodundan daha sonra yapılacak bir geliştirme algoritmada açıklar oluşturabilir ve testi çalıştırdığımızda bir sorun görmeyebiliriz fakat canlı ortamdan hatalar almamız kuvvetle muhtemeldir.

Aşağıdaki verify methoduna baktığımızda isteğimiz sadece bir kez istediğimiz methodun herhangi bir integer değer ile çalışması. Tabii herhangi bir değer değil de kendimiz de o method çalıştığında aldığı değeri verebilirdik fakat ben farklı kullanımları göstermek adına bu şekilde bıraktım.

verify(realtyRepository, times(1)).findAllByUserId(Mockito.anyInt());

Bir de aşağıdaki iki kullanımın önemli olduğunu düşünüyorum. Nedeni ise şöyle bahsetmeye çalıştığım gibi eğer userService tekrar kullanılmayacaksa bunu garanti altına almam gerekir ki daha sonra bu method refactor edildiğinde yanlışlıkla userService tekrar kullanılmasın.

verifyNoMoreInteractions(userService);

verifyNoInteractions(bannerServiceClient);

Not: Methodun refactor edilmesinden bahsettim. Buradadan da anlaşılacağı üzere bir methodu refactor edecekseniz öncelikle test yazmanız sizin için en güvenli yol olacaktır. Çünkü diğer türlü farklı kodlarla aynı işi yaptığınızı kanıtlayamazsınız.

Üçüncü testimize bakalım.

 @Test
void it_should_throw_runtime_exception_when_banner_response_status_is_not_created_for_indivual_user() {

// given

Optional<User> user = getUser(1, UserType.INDIVIDUAL);

Mockito.when(userService.getById(1)).thenReturn(user);

Mockito.when(realtyRepository.findAllByUserId(1)).thenReturn(getRealtyList(1));

Mockito.when(bannerServiceClient.create(Mockito.any(Banner.class)))
.thenReturn(getBannerResponse(HttpStatus.BAD_REQUEST));

// when

Throwable exception = catchThrowable(() -> realtyService.create(getRealtyRequest(1)));

// then
assertThat(exception).isInstanceOf(RuntimeException.class);

assertThat(exception.getMessage()).isEqualTo("Banner kaydedilemedi!");

verify(userService, times(1)).getById(1);

verifyNoMoreInteractions(userService);

verify(realtyRepository, times(1)).findAllByUserId(1);

Mockito.verify(realtyRepository, times(0)).save(Mockito.any(Realty.class));

verify(bannerServiceClient, times(1)).create(Mockito.any(Banner.class));

}

Dördüncü testimiz ise beklediğimiz başarılı durumların oluştuğunda

 @Test
void it_should_save_realty() {

// given

Optional<User> user = getUser(1, UserType.INDIVIDUAL);

Mockito.when(userService.getById(1)).thenReturn(user);

Mockito.when(realtyRepository.findAllByUserId(1)).thenReturn(getRealtyList(4));

Mockito.when(bannerServiceClient.create(Mockito.any(Banner.class)))
.thenReturn(getBannerResponse(HttpStatus.CREATED));

Realty realty = getRealty(1);

Mockito.when(realtyRepository.save(Mockito.any())).thenReturn(realty);

// when

Realty responseRealty = realtyService.create(getRealtyRequest(1));

// then

verify(userService, times(1)).getById(1);

verifyNoMoreInteractions(userService);

verify(realtyRepository, times(1)).findAllByUserId(1);

Mockito.verify(realtyRepository, times(1)).save(Mockito.any(Realty.class));

verifyNoMoreInteractions(realtyRepository);

verify(bannerServiceClient, times(1)).create(Mockito.any(Banner.class));

verifyNoMoreInteractions(bannerServiceClient);

assertThat(responseRealty).isNotNull();

assertThat(responseRealty.getId()).isEqualTo(realty.getId());

assertThat(responseRealty.getTitle()).isEqualTo(realty.getTitle());

assertThat(responseRealty.getProvince()).isEqualTo(realty.getProvince());

assertThat(responseRealty.getStatus()).isEqualTo(realty.getStatus());

}

Görüldüğü üzere bir method için dört tane test yazdık ve daha fazla yazabiliriz.

Bütün kodlara aşağıdaki linkten ulaşabilirsiniz.

https://gist.github.com/cemdrman/1ee116b3a5eaaf1ab6cd5a46091e3e59

Bir çok konunun yer aldığı diğer yazılar için bilisim.io ziyaret edebilirsiniz.

Unit test yazılarına devam ediyor olacağız, yeni yazılarda görüşmek üzere.

Faydalı olması dileğiyle.

--

--