Test MockBean에 대해 알아보자

고동현·2024년 6월 20일
0

"더 깊이, 더 넓게"

목록 보기
5/12

Test 환경

Prove 프로젝트를 하던중에 Prove 업로드가 정상적으로 되는지 확인을하는 Test Code가 필요하게 되었다.


    public void completeProve(ProveDto proveDto, List<MultipartFile> uploadImages, Long proveId) {
        Prove prove = proveRepository.findById(proveId).orElseThrow(() -> new IllegalArgumentException());
        List<Image> imageList = new ArrayList<>();
        List<ImageDto> imageDtoList = new ArrayList<>();

        if(uploadImages != null){
            for(MultipartFile img : uploadImages){
                ImageDto imageDto = awsS3Service.uploadFile(img);
                imageDtoList.add(imageDto);
            }
        }

        makeImageList(imageList,imageDtoList);

        prove.completeProve(imageList,proveDto);

        String username = getUsername();
        UserEntity user = userRepository.findByUsername(username);

        user.complit();

        proveRepository.save(prove);
    }

원하는 Test내용

  1. proveId에 해당하는 Prove에 ProveDto의 내용이 제대로 들어가는가?
  2. prove에 List<MultippartFile'> uploadImages가 prove의 ImageList 필드에 제대로 들어가는가?

이 두가지를 Test해보고 싶었다.

그래서 Test를 짜려고하니..

문제1

Test code를 짤때 서비스코드에 Repository와 다른 비즈니스 로직들이 엉켜있어, Test 범위가 넓어진다는 것입니다.

이 말이 무었이냐.
나는 1번에 해당하는 ProveDto에 있는 내용이 Prove에 들어갔는지를 검사하고싶은건데, 이를 위해서 실제 Prove를 Repository에 저장해야하고, 또 Prove에 해당하는 UserEntity를 설정하기 위해서 UserRepository를 이용해야하는등..
내가 원하고자하는 비즈니스 로직 Test에 집중할 수 없다는 것이다.

그래서 Mock를 사용하였다.
Mock은 껍데기만 있는 객체를 얘기한다.
즉, 해당 Bean의 어떤 메소드가 어떤 값이 입력 되면 어떤 값이 리턴 되어야 한다는 내용 모두 개발자 필요에 의해서 조작이 가능하게 된다.


    @BeforeEach
    public void setUp() {
        proveDto = new ProveDto(
                "open",
                1L,
                "short description",
                "success",
                "tag1,tag2",
                10L,
                LocalDateTime.now().minusDays(1),
                LocalDateTime.now().plusDays(1)
        );// 필요한 필드 설정
        uploadImages = new ArrayList<>();
        uploadImages.add(new MockMultipartFile("image1", "image1.jpg", "image/jpeg", new byte[0]));
        proveId = 1L;

        prove = new Prove();
        user = new UserEntity(); 
 }

우선 BeforeEach로 setup을 해준다.
Prove와 UserEntity를 굳이 내가 초기화 하지 않아도 된다. 왜? Prove에 제대로 DTO값이 들어갔는지만 확인하면 되서.

또한 MockMultipartFile을 통해서 실제 이미지를 가지고 test하지 않아도 Mock로 손쉽게 test할 수 있다.

	@Mock
    private ProveRepository proveRepository;

    @Mock
    private UserRepository userRepository;
    
    @Mock
    private AwsS3Service awsS3Service;

    @InjectMocks
    
    private ProveService proveService;
     private ProveDto proveDto;
    private List<MultipartFile> uploadImages;
    private Long proveId;
    private Prove prove;
    private UserEntity user;

InjectMocks를 통해 Mock 객체를 주입할 수 있다.

    @Test
  
    public void testCompleteProve() {
        // Mocking
        when(proveRepository.findById(proveId)).thenReturn(Optional.of(prove));
        when(awsS3Service.uploadFile(any(MultipartFile.class))).thenReturn(new ImageDto("image1.jpg", "http://example.com/image1.jpg"));
        when(userRepository.findByUsername(anyString())).thenReturn(user);

when절을 통해
proveId를 넘겨주면 return으로 prove를 return할수있다.

awsS3Service부분이 정말 좋다. 만약 내가 이미지를 가지고 Test를 하려면 S3에 실제로 올려서 확인해 봐야 할것이다. 그러나, Mock를 사용해서 S3 버킷에 담지 않아도, 그냥 return으로 해당 Image객체를 return하게 하였다.

User도 실제 Repository에 user를 저장해서 해당 저장된 User엔티티를 findByUsername메서드로 부르는,, 이런 코드를 직접 다 작성하는게 아니라, 그냥 user를 return하게 하였다.

 // Method call
        proveService.completeProve(proveDto, uploadImages, proveId);

        // Verify interactions
        assertThat(prove.getShortWord()).isEqualTo("short description");
        boolean containsImage1 = prove.getImgList().stream()
                .anyMatch(image -> "image1.jpg".equals(image.getImgName()));
        System.out.println(prove.getImgList().get(0).getImgName());
        assertThat(containsImage1).isTrue();

그다음에 검증하고 싶은 compleProve를 호출한 뒤에
proveDto의 showrtWord필드가 "short description"이므로 prove에 정상적으로 저장되었는지 확인하기위해서 prove.getShortWord()를 통해서 확인 하였다.

image도 제대로 저장되었는지 확인하기위해서 getImgList를 호출하고 stream으로 when(awsS3Service.uploadFile(any(MultipartFile.class))).thenReturn(new ImageDto("image1.jpg", "http://example.com/image1.jpg"));
여기서 ImageDto의 이름이 image1.jpg이므로 해당 이름으로 image가 들어갔으므로 image1.jpg와 동일한지 확인하였다.

문제 2

해당 프로젝트에서 JWT를 사용하고 있는데,
private static String getUsername() {
String username = SecurityContextHolder.getContext().getAuthentication().getName();
return username;
}
compleProve메서드 내에있는 getUsername을 호출시 오류가 발생하였다.

지금 내가 Authentication을 받지 않은 상태에서 접근을 하려고 하니 null인데 null.getName()을 호출하니 NullPoinetException이 터졌다.

이것도 MOCK로 해결할 수 있을까?

해결할 수 있다.

 @BeforeEach
    public void setUp() {
        proveDto = new ProveDto(
                "open",
                1L,
                "short description",
                "success",
                "tag1,tag2",
                10L,
                LocalDateTime.now().minusDays(1),
                LocalDateTime.now().plusDays(1)
        );// 필요한 필드 설정
        uploadImages = new ArrayList<>();
        uploadImages.add(new MockMultipartFile("image1", "image1.jpg", "image/jpeg", new byte[0]));
        proveId = 1L;

        prove = new Prove(); // 필요한 필드 설정
        user = new UserEntity(); // 필요한 필드 설정
        // SecurityContext와 Authentication 객체를 모킹
        SecurityContext securityContext = mock(SecurityContext.class);
        Authentication authentication = mock(Authentication.class);

        // SecurityContextHolder에 모킹된 SecurityContext 설정
        SecurityContextHolder.setContext(securityContext);

        // 모킹된 Authentication 객체를 SecurityContext에 설정
        when(securityContext.getAuthentication()).thenReturn(authentication);

        // 인증된 사용자의 이름을 "testuser"로 설정
        when(authentication.getName()).thenReturn("testuser");
    }

setup 부분에 SecurityContext와 관련된 설정을 추가하였다.
SecurityContext와 Authentication을 Mocking해주었고,
또한 when절을 통해 authentication.getName()을 호출시 testuser를 반환해주었다.

다시 test를 수행하면

proveDto에 있는 shortword가 제대로 prove에 들어갔다는 것과,
imageList에 있는 Multipart 이미지가 제대로 prove의 ImgList에 들어갔는것을 확인할수 있었다.

Mock를 활용하여

  • 테스트 대상 코드 격리
  • 테스트 속도 개선
  • 예측 불가능한 실행 요소 제거
  • 특수한 상황 테스트 가능
  • 감춰진 정보를 확인 가능

등의 이점을 알아 볼 수 있는 경험이었다.

profile
항상 Why?[왜썻는지] What?[이를 통해 무엇을 얻었는지 생각하겠습니다.]

0개의 댓글