본문 바로가기
Back-end/테스트

Spring Boot Unit Testing - Mocking with Mockito - @MockBean, ReflectionTestUtils

by javapp 자바앱 2022. 10. 21.
728x90

 

 

 

더블 테스트

 

Service - DAO                     - DB

             - DAO 더블 테스트        

 

 

목적

DAO 더블 테스트 에서 테스트 후

DAO 실제 세계에 적용

 

 

더블 테스트의 기술 : mocking

  • 주어진 클래스를 따로 테스트할 수 있게 해줍니다.
  • 주어진 클래스와 종속성 사이에서 상호작용
  • Configuration 최소화 , 종속성의 가용성
  • DAO, DB, REAT API 등 

>> Spring Boot는 Mocking 프레임워크 중 Mockito 지원

 

 


 

Mock 테스트 과정

  1. DAO를 위한 Mock 생성
  2. Service 에 Mock 주입
  3. Set up expectations(기대치)
  4. Execute test 메소드
  5. Assert results
  6. Verify 메소드 호출

 

 

@Mock

테스트를 할 때 필요한 실제 객체와 동일한 모의 객체를 만들어 테스트 효용성 높이기 위해 사용

 

 


 

더블 테스트를 하기 위한

   테스트 클래스 생성

package com.luv2code.test;

@SpringBootTest(classes= MvcTestingExampleApplication.class) //패키지 경로가 메인이랑 다르기 때문에 메인클래스 설정
public class MockAnnotationTest
{
    @Autowired
    ApplicationContext context;

    @Autowired
    CollegeStudent studentOne;

    @Autowired
    StudentGrades studentGrades;

    // DAO 더블 테스트
    @Mock
    private ApplicationDao applicationDao;

    // @Mock or @Spy 에 대해서만 주입
    @InjectMocks
    private ApplicationService applicationService;

    @BeforeEach
    public void beforeEach(){
        studentOne.setFirstname("java");
        studentOne.setLastname("app");
        studentOne.setEmailAddress("javapp@tistory.com");
        studentOne.setStudentGrades(studentGrades);
    }

    @DisplayName("when % verify")
    @Test
    public void assertEqualsTestAddGrades(){
        // stubbing
        when(applicationDao.addGradeResultsForSingleClass(studentGrades.getMathGradeResults()))
                .thenReturn(100.0);

        assertEquals(100,applicationService.addGradeResultsForSingleClass(
                studentOne.getStudentGrades().getMathGradeResults()));

        // verify: dao 호출 확인 후...
        verify(applicationDao).addGradeResultsForSingleClass(studentGrades.getMathGradeResults());
        verify(applicationDao, times(1)).addGradeResultsForSingleClass(studentGrades.getMathGradeResults());
        // times, 뒤에 메소드가 n 번 호출됐는지 검증
    }
}

 

1.

@Mock 애노테이션을 이용해서 mock객체 생성

 

    @Mock
    private ApplicationDao applicationDao;

 


2.

@injectMocks 
@Mock or @Spy 에 대해서만 주입

Service 내부 dao에 주입

    @InjectMocks
    private ApplicationService applicationService;

 

 

3. 4. 5. 6.

    @DisplayName("when % verify")
    @Test
    public void assertEqualsTestAddGrades(){
        // stubbing, getMathGradeResults()의 값을 100으로 설정
        when(applicationDao.addGradeResultsForSingleClass(studentGrades.getMathGradeResults()))
                .thenReturn(100.0);

        assertEquals(100,applicationService.addGradeResultsForSingleClass(
                studentOne.getStudentGrades().getMathGradeResults()));

        // verify: dao 호출 확인 후...
        verify(applicationDao).addGradeResultsForSingleClass(studentGrades.getMathGradeResults());
        verify(applicationDao, times(1)).addGradeResultsForSingleClass(studentGrades.getMathGradeResults());
        // times, 뒤에 메소드가 n 번 호출됐는지 검증
    }

.thenReturn(n) : applicationDao.addGradeResultsForSingleClass(studentGrades.getMathGradeResults()) 값에 n을 넣어줌.

 


 

@MockBean

스프링 컨텍스트에 Mock객체를 빈으로 추가

 

@Mock and @InjectMocks 대신 사용

include @Mock

@InjectMocks 대신 @Autowired 사용

    // DAO 더블 테스트
    //@Mock
    @MockBean
    private ApplicationDao applicationDao;

    // @Mock or @Spy 에 대해서만 주입
    //@InjectMocks
    @Autowired
    private ApplicationService applicationService;

 

테스트 추가

    @DisplayName("Find GPA")
    @Test
    public void assertEqualsTestFindGpa(){
        when(applicationDao.findGradePointAverage(studentGrades.getMathGradeResults())).thenReturn(88.31);
        assertEquals(88.31, applicationService.findGradePointAverage(studentOne.getStudentGrades().getMathGradeResults()));
    }

    @DisplayName("Not Null")
    @Test
    public void testAssertNotNull(){
        when(applicationDao.checkNull(studentGrades.getMathGradeResults())).thenReturn(true);
        assertNotNull(applicationService.checkNull(studentOne.getStudentGrades().getMathGradeResults()),"not null 이여아 한다.");
    }

    @DisplayName("Throw runtime error")
    @Test
    public void throwRuntimeError(){
        // null 객체 생성
        CollegeStudent nullStudent = (CollegeStudent) context.getBean("collegeStudent");

        // null 일때 예외를 던진다.
        doThrow(new RuntimeException()).when(applicationDao).checkNull(nullStudent);

        // 예외가 RuntimeException 인지
        assertThrows(RuntimeException.class, ()->{
            applicationService.checkNull(nullStudent);
        });

        // applicationDao.checkNull() 메소드가 1번 실행되었는지
        verify(applicationDao, times(1)).checkNull(nullStudent);
    }

    @DisplayName("Multiple Stubbing")
    @Test
    public void stubbingConsecutiveCalls(){
        CollegeStudent nullStudent = (CollegeStudent) context.getBean("collegeStudent");

        when(applicationDao.checkNull(nullStudent)).thenThrow(new RuntimeException())
                        .thenReturn("예외 두번 실행");

        // 예외가 RuntimeException 인지
        assertThrows(RuntimeException.class, ()->{
            applicationService.checkNull(nullStudent);
        });

        assertEquals("예외 두번 실행",applicationService.checkNull(nullStudent));

        // dao 와 service 에서 .checkNull을 두번 실행
        verify(applicationDao, times(2)).checkNull(nullStudent);
    }

 

 


 

 

리플렉션

ReflectionTestUtils

non-public 한 필드를 직접 get/set

비공개 메소드 호출 가능

추가적으로 엔티티에는 @Setter 메소드를 선언하면 좋지 않습니다. 엔티티 혹은 객체는 불변한 성질을 가져야 하기 때문입니다. 그래서 set 함수가 없을 경우 ReflectionTestUtils.setField(..) 을 통해 값을 설정할 수 있습니다.

 

 

 

 

CollegeStudent.java

    private int id;
	
    private String getFirstNameAndId(){
        return getFirstname()+" "+getId();
    }

 

@SpringBootTest(classes = MvcTestingExampleApplication.class)
public class ReflectionTestUtilsTest {

    @Autowired
    ApplicationContext context;

    @Autowired
    CollegeStudent studentOne;

    @Autowired
    StudentGrades studentGrades;

    @BeforeEach
    public void studentBeforeEach() {
        studentOne.setFirstname("java");
        studentOne.setLastname("app");
        studentOne.setEmailAddress("javapp@daum.net");
        studentOne.setStudentGrades(studentGrades);

        ReflectionTestUtils.setField(studentOne, "id", 1);
        ReflectionTestUtils.setField(studentOne, "studentGrades",
                new StudentGrades(new ArrayList<>(Arrays.asList(
                        100.0, 85.0, 76.5, 50.0
                ))));
    }

    @Test
    public void getPrivateField(){
        assertEquals(1, ReflectionTestUtils.getField(studentOne,"id"));
    }
    @Test
    public void invokePrivateMethod(){
        assertEquals("javapp 1",
                ReflectionTestUtils.invokeMethod(studentOne,"getFirstNameAndId"),
                "private 메소드 호출 실패");
    }

}

 

assertEquals(1, ReflectionTestUtils.getField(studentOne,"id"));
assertEquals("javapp 1",
        ReflectionTestUtils.invokeMethod(studentOne,"getFirstNameAndId"),
        "private 메소드 호출 실패");

 

 

 

Mockito를 이용한 MockTest

필자는 원하는 기능을 제공하는 Mock 라이브러리를 찾지 못해 (또는 완전히 학습하지 못해?) 필요한 기능을 제공하는 간단한 라이브러리를 직접 만들어서 사용한 적도 있다. 그런데, 매우 맘에

javacan.tistory.com

 

댓글