java&spring

Mockito를 사용하여 JUnit Test하기

sungjine 2017. 4. 25. 21:27
반응형

Mockito Framework는 목 객체를 만들어 주는 Framework로 테스트하는 데 유용하게 쓸 수 있다.

 

목 객체란 테스트를 진행할 때 해당 코드 외의 의존하는 객체를 가짜로 만든 것을 지칭한다. 이러한 목 객체는 테스트하고 싶은 코드에 대해서 정확하게 테스트하기 위해서 사용된다.

1. 해당 코드가 아닌 의존관계에 있는 다른 코드에서 오류가 나거나 없는 경우

2. 네트워크나 DB 연결 등 외부의 요인에 영향을 받는 경우

 

이제 Mockito에 대한 여러 가지 활용법을 코드로 알아보자

 

먼저 필요한 라이브러리를 빌드하자. (Maven)

<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-all</artifactId>
    <version>1.10.19</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
</dependency>

다음은 verify에 대한 테스트 코드이다.

public void verifyTest() {
    List<String> testMockList = mock(ArrayList.class);
    
    testMockList.add("1");
    testMockList.add("2");
    testMockList.add("3");
    testMockList.add("3");
    
    verify(testMockList, atLeastOnce()).add("1");
    verify(testMockList, atLeast(2)).add("3");
    verify(testMockList, atMost(4)).add(anyString());
    verify(testMockList, atMost(4)).add("123");
    verify(testMockList, times(1)).add("1");
    verify(testMockList, times(2)).add("3");
    verify(testMockList, times(4)).add(anyString());
    verify(testMockList, never()).add("4");
    verify(testMockList, times(0)).add("4");
    assertNull("1", testMockList.get(0)); 
}
 
코드를 보면 ArrayList로 목 객체를 만들었다. 그리고 값을 add해주고 verify()를 통해서 목 객체가 잘 만들어졌는지 확인하는 코드이다.
 

atLeastOnce() 메소드는 적어도 한번 .add() 메소드가 실행이 됐는지 확인하는 것이다.

atLeast(2) 메소드는 .add("3") 메소드가 최소한 지정한 횟수만큼 실행이 되었는지 확인하는 것이다.

atMost(4) 메소드는 .add() 메소드가 최대 실행된 횟수를 확인하는 것이다.

times(2) 메소드는 .add("3") 메소드가 지정한 횟수만큼 실행이 되었는지 확인하는 것이다.

* (atLeast(2)와 time(2)는 같아 보일 수도 있지만 atLeast(1).add("3")도 통과하는 반면 time(1).add("3")은 통과하지 못한다.)

times(4).add(anyString()); 에서 anyString()은 Matchers의 메소드로 모든 String값을 뜻한다. 즉 atMost(4)와 같다고 생각하면 된다.

naver()와 times(0)은 같다고 생각하면 된다.

마지막에 assertNull()은 목 객체에 실제로는 값이 들어가지 않는 다는 것을 보기 위한 테스트 코드이다.

 

다음은 when then에 대한 테스트 코드이다.

public void whenThenTest() {
    Map<String, String> testMockMap = mock(Map.class);
    
    when(testMockMap.get("id")).thenReturn("id");
    when(testMockMap.get("pw")).thenReturn("pw");
    verify(testMockMap, atMost(2)).get(anyString());
    assertThat("id", is(testMockMap.get("id")));
    assertThat("pw", is(testMockMap.get("pw")));
}

 

코드를 보면 HashMap을 목 객체로 만들었다. 그리고 when() 메소드를 통해서 목 객체가 get() 메소드를 호출하면 ThenReturn()메소드에 적어놓은 내용이 출력되게 한 것이다.

 

먼저 verify를 통해서 목 객체가 잘 만들어졌는지 확인한다. 그다음은 목 객체에서 값이 잘 return되는지 확인한다.

 

다음은 Exception을 throw하는 것에 대한 테스트 코드이다.

@Test(expected=RuntimeException.class)
public void throwExceptionTest() {
    Map<String, String> testMockMap = mock(Map.class);
    
    when(testMockMap.get("name4")).thenThrow(new RuntimeException());
    testMockMap.get("name4");
}
 
목 객체를 만든 후 이번에는 when(), thenThrow()메소드를 사용한 후 when()메소드에 작성한 대로 목 객체의 메소드를 호출하면 Exception을 throw한다.

 

반응형

 

다음은 클레스를 하나 만들어서 진행하는 테스트 코드이다. 그러니 테스트를 진행하기 전에 클레스를 만들자.
 
User.java
public class User {
    private String name;
    private String email;
    
    public User(){}
    public User(String name, String email){
        this.name = name;
        this.email = email;
    }
    
    public String getName() {
        return name;
    }
    public void setName(String name) { 
        this.name = name; 
    } 
    public String getEmail() { 
        return email; 
    } 
    public void setEmail(String email) { 
        this.email = email;
    } 
}
 
UserDao.java
public class UserDao {
    public User findUser(String email) {
        return new User("name", email); 
    }
    
    public User findUser() { 
        return new User("name", "email"); 
    } 
}
 
자 이제 테스트를 진행하자
public void answerTest() {
    UserDao testMockUser = mock(UserDao.class);
    
    when(testMockUser.findUser("em")).thenAnswer(new Answer<User>() {
        public User answer(InvocationOnMock invocation) throws Throwable {
            return new User("nm", "email");
        }
    });
    
    User user = testMockUser.findUser("em");
    
    assertThat("nm", is(user.getName()));
    assertThat("email", is(user.getEmail())); 
}

 

위 테스트 코드는 UserDao클레스를 목 객체로 만들고 목 객체에서 findUser("email") 메소드를 호출할 때 User객체를 만들어서 리턴하게 했다. 실제로 테스트해보면 잘 나오는 것을 볼 수 있다.

 

다음 테스트는 Spy코드이다.

public void spyTest() {
    List<Integer> list = new ArrayList<>();
    List<Integer> spy = Mockito.spy(list);
    
    when(spy.size()).thenReturn(100);
    spy.add(1);
    spy.add(2);
    
    assertEquals(spy.size(), 100);
    assertThat(spy.get(0), is(1));
    verify(spy).add(1);
    verify(spy).add(2);
}

 

목 객체와 마찬가지로 Spy는 가짜 객체이다. 다른 점이 있다면 Spy는 실제 객체처럼 메소드가 동작한다는 것이다.

위 코드에서는 when(spy.size()).thenReturn(100); 로 spy가 목 객체처럼 동작한다는 것을 보여주고자 했고 assertEquals(spy.size(), 100); 로 잘 동작하는 것을 볼 수 있다. 그리고 add()로 데이터를 넣고 get() 메소드로 잘 불러와 지는 것으로 목 객체와는 다르게 실제 객체처럼 움직인다는 것을 보여주고 있다.

 

--- 추가 ---

만약 어노테이션을 통해서 목 객체를 사용하고자 한다면 아래 코드를 참고하자

@RunWith(MockitoJUnitRunner.class)
public class MvcMockTest {
    @Mock private UserDao userDao;
    @InjectMocks private UserService userService;
    
    @Test public void findUserTest(){
        when(userDao.findUser()).thenReturn(new User("name", "email"));
        User user = userService.findUser();
        assertEquals(user.getEmail(), "email");
        assertEquals(user.getName(), "name"); 
    }
    
    @Test public void findUserByEmailTest(){ 
        when(userDao.findUser("email")).thenReturn(new User("name", "email")); 
        User user = userService.findUser("email");
        assertEquals(user.getEmail(), "email"); 
        assertEquals(user.getName(), "name"); 
    } 
}

 

@Mock : 목 객체로 만들 객체

@InjectMocks : 목 객체를 injection받을 객체

반응형