이 게시물에서 우리는 모의(mocking)가 무엇인지, 왜 사용하는지, 그리고 가장 많이 사용되는 Java용 모의 라이브러리를 사용하여 동일한 테스트 사례를 모의하는 방법에 대한 몇 가지 예에 대해 이야기하겠습니다.
모의 개념에 대한 몇 가지 형식적/준형식적 정의부터 시작하겠습니다. 그런 다음 테스트 중인 사례를 제시하고 각 라이브러리에 대한 예제를 추적한 후 몇 가지 결론을 내릴 것입니다. 선택한 라이브러리는 Mockito, EasyMock 및 JMockit입니다.
조롱의 기본 사항을 이미 알고 있다고 생각되면 다음 세 개의 하위 섹션을 읽지 않고 섹션 2로 건너뛸 수도 있습니다.
테스트(TDD, ATDD 또는 BDD)를 중심으로 한 일부 주도적 개발 방법론에 따라 이미 코드를 작성했다고 가정하겠습니다. 또는 단순히 해당 기능을 달성하기 위해 종속성에 의존하는 기존 클래스에 대한 테스트를 만들고 싶을 수도 있습니다.
어떤 경우든, 클래스를 단위 테스트할 때 우리는 해당 클래스의 기능만 테스트하고 종속성은 테스트하지 않습니다(그 구현을 신뢰하거나 직접 테스트할 것이기 때문입니다).
이를 달성하려면 해당 종속성을 제어할 수 있는 대체 개체를 테스트 대상 개체에 제공해야 합니다. 이런 식으로 우리는 극단적인 반환 값, 예외 발생을 강제하거나 단순히 시간이 많이 걸리는 메서드를 고정된 반환 값으로 줄일 수 있습니다.
이 제어된 교체는 모의이며 테스트 코딩을 단순화하고 테스트 실행 시간을 줄이는 데 도움이 됩니다.
모든 사람이 모의 객체에 대해 알아야 할 기본 사항을 요약한 Martin Fowler가 작성한 기사에서 네 가지 정의를 살펴보겠습니다.
Dummy 객체는 전달되지만 실제로는 사용되지 않습니다. 일반적으로 매개변수 목록을 채우는 데 사용됩니다.
Fake 객체에는 작동하는 구현이 있지만 일반적으로 프로덕션에 적합하지 않게 만드는 몇 가지 지름길을 사용합니다(인메모리 데이터베이스가 좋은 예입니다).
Stub은 테스트 중에 이루어진 호출에 대해 미리 준비된 답변을 제공하며 일반적으로 테스트를 위해 프로그래밍된 것 이외의 어떤 것에도 전혀 응답하지 않습니다. 스텁은 '보낸' 메시지를 기억하는 이메일 게이트웨이 스텁과 같이 통화에 대한 정보를 기록하거나 '보낸' 메시지 수만 기록할 수도 있습니다.
Mocks는 우리가 여기서 말하는 것입니다. 수신할 것으로 예상되는 호출의 사양을 형성하는 기대로 미리 프로그래밍된 객체입니다.
모든 것이 mocking되어서는 안 됩니다. 때로는 실제 이점이 거의 없는 메서드/기능을 조롱하는 통합 테스트를 수행하는 것이 더 나을 때도 있습니다. LoginDao를 테스트하는 테스트 케이스(다음 섹션에 표시됨)입니다.
LoginDao는 DB 액세스를 위해 일부 타사 라이브러리를 사용하고 이를 mocking하는 것은 매개변수가 호출을 위해 준비되었는지 확인하는 것으로만 구성되지만 호출이 원하는 데이터를 반환하는지 테스트해야 합니다.
이러한 이유로 이 예제에는 포함되지 않습니다. (타사 라이브러리 호출에 대한 모의 호출을 사용하는 단위 테스트와 타사 라이브러리의 실제 성능을 테스트하기 위해 DBUnit을 사용한 통합 테스트를 모두 작성할 수 있지만)
이전 섹션의 모든 내용을 염두에 두고 매우 일반적인 테스트 사례를 제안하고 이를 모의 개체를 사용하여 테스트하는 방법(모의 개체를 사용하는 것이 적합한 경우)을 제안해 보겠습니다. 이는 나중에 다양한 모의 라이브러리를 비교할 수 있는 공통 시나리오를 갖는 데 도움이 될 것입니다.
제안된 테스트 케이스는 계층화된 아키텍처를 갖춘 애플리케이션의 로그인 프로세스입니다.
로그인 요청은 DAO(DB에서 사용자 자격 증명을 찾는)를 사용하는 서비스를 사용하는 컨트롤러에 의해 처리됩니다. 우리는 각 레이어의 구현을 너무 깊게 다루지 않고 각 레이어 구성 요소 간의 상호 작용에 더 중점을 둘 것입니다.
이런 식으로 LoginController, LoginService 및 LoginDAO를 갖게 됩니다. 설명을 위해 다이어그램을 살펴보겠습니다.

이제 테스트 사례에 사용된 구현을 따라가므로 테스트에서 무슨 일이 일어나고 있는지(또는 무슨 일이 일어나야 하는지) 이해할 수 있습니다.
모든 작업에 사용되는 모델인 UserForm부터 시작하겠습니다. 이 모델은 사용자의 이름과 비밀번호만 보유하며(간단화하기 위해 공개 액세스 한정자를 사용하고 있습니다) 해당 속성에 대한 모의를 허용하는 username 필드에 대한 getter 메서드를 사용합니다.
public class UserForm {
public String password;
public String username;
public String getUsername() {
return username;
}
}
필요한 경우 모의할 수 있도록 메서드만 있기를 원하기 때문에 기능이 없는 LoginDAO를 살펴보겠습니다.
public class LoginDao {
public int login(UserForm userForm) {
return 0;
}
}
LoginDao는 LoginService의 login 메소드에서 사용됩니다. LoginService에는 해당 모형을 테스트하기 위해 void를 반환하는 setCurrentUser 메서드도 있습니다.
public class LoginService {
private LoginDao loginDao;
private String currentUser;
public boolean login(UserForm userForm) {
assert null != userForm;
int loginResults = loginDao.login(userForm);
switch (loginResults) {
case 1:
return true;
default:
return false;
}
}
public void setCurrentUser(String username) {
if (null != username) {
this.currentUser = username;
}
}
}
마지막으로 LoginController는 login 메서드로 LoginService를 사용합니다. 여기에는 다음이 포함됩니다.
public class LoginController {
public LoginService loginService;
public String login(UserForm userForm) {
if (null == userForm) {
return "ERROR";
} else {
boolean logged;
try {
logged = loginService.login(userForm);
} catch (Exception e) {
return "ERROR";
}
if (logged) {
loginService.setCurrentUser(userForm.getUsername());
return "OK";
} else {
return "KO";
}
}
}
}
이제 우리가 테스트하려는 것이 무엇인지 알았으니 각 라이브러리에서 이를 어떻게 모의할 것인지 살펴보겠습니다.
Mockito의 경우 버전 2.8.9를 사용합니다.
모의 객체를 생성하고 사용하는 가장 쉬운 방법은 @Mock 및 @InjectMocks 어노테이션을 사용하는 것입니다. 첫 번째는 필드를 정의하는 데 사용되는 클래스에 대한 모의를 생성하고 두 번째는 생성된 모의를 어노테이션이 달린 모의에 주입하려고 시도합니다.
부분 모의(모의되지 않은 메서드에서 일반 구현을 사용하는 모의)를 생성할 수 있는 @Spy와 같은 더 많은 어노테이션이 있습니다.
즉, 이 모든 "마법"이 작동하려면 해당 모의 객체를 사용하는 테스트를 실행하기 전에 MockitoAnnotations.initMocks(this)를 호출해야 합니다. 이는 일반적으로 @Before 어노테이션이 달린 메서드에서 수행됩니다. MockitoJUnitRunner를 사용할 수도 있습니다.
public class LoginControllerTest {
@Mock
private LoginDao loginDao;
@Spy
@InjectMocks
private LoginService spiedLoginService;
@Mock
private LoginService loginService;
@InjectMocks
private LoginController loginController;
@Before
public void setUp() {
loginController = new LoginController();
MockitoAnnotations.initMocks(this);
}
}
EasyMock의 경우 버전 3.4(Javadoc)를 사용합니다. EasyMock을 사용하면 모의 객체가 "작동"을 시작하려면 모든 테스트 메서드에서 EasyMock.replay(mock)를 호출해야 합니다. 그렇지 않으면 예외가 발생합니다.
모의 클래스와 테스트된 클래스는 어노테이션을 통해 정의할 수도 있지만 이 경우 작동하도록 정적 메서드를 호출하는 대신 테스트 클래스에 EasyMockRunner를 사용합니다.
모의는 @Mock 어노테이션으로 생성되고 테스트된 객체는 @TestSubject로 생성됩니다(생성된 모의에서 종속성이 주입됩니다). 테스트된 개체는 인라인으로 생성되어야 합니다.
@RunWith(EasyMockRunner.class)
public class LoginControllerTest {
@Mock
private LoginDao loginDao;
@Mock
private LoginService loginService;
@TestSubject
private LoginController loginController = new LoginController();
}
JMockit의 경우 버전 1.49(Javadoc)를 사용합니다.
JMockit 설정은 부분 모의에 대한 특정 어노테이션이 없다는 점을 제외하면 Mockito만큼 쉽습니다(그리고 실제로도 필요하지 않습니다). 또한 더 이상 JUnit 4 테스트를 위해 @RunWith(JMockit.class)로 테스트 클래스에 어노테이션을 달 필요가 없습니다.
모의는 @Injectable 어노테이션(모의 인스턴스 하나만 생성) 또는 @Mocked 어노테이션(어노테이션이 달린 필드 클래스의 모든 인스턴스에 대해 모의를 생성)을 사용하여 정의됩니다.
@Tested 어노테이션을 사용하여 테스트된 인스턴스가 생성되고 해당 종속성이 주입됩니다.
public class LoginControllerIntegrationTest {
@Injectable
private LoginDao loginDao;
@Injectable
private LoginService loginService;
@Tested
private LoginController loginController;
}
Mockito에서 모의 개체가 호출을 받지 않았는지 확인하기 위해 모의 개체를 허용하는 verifyNoInteractions() 메서드가 있습니다.
@Test
public void assertThatNoMethodHasBeenCalled() {
loginController.login(null);
Mockito.verifyNoInteractions(loginService);
}
모의 개체가 호출을 받지 않았는지 확인하기 위해 동작을 지정하지 않고 모의 개체를 재생한 후 마지막으로 확인합니다.
@Test
public void assertThatNoMethodHasBeenCalled() {
EasyMock.replay(loginService);
loginController.login(null);
EasyMock.verify(loginService);
}
모의 개체가 호출을 받지 않았는지 확인하기 위해 해당 모의 개체에 대한 기대치를 지정하지 않고 해당 모의 개체에 대해 FullVerifications(mock)을 수행합니다.
@Test
public void assertThatNoMethodHasBeenCalled() {
loginController.login(null);
new FullVerifications(loginService) {};
}
모의 메서드 호출의 경우 Mockito.when(mock.method(args)).thenReturn(value)을 사용할 수 있습니다. 여기서는 더 많은 매개변수로 추가하기만 하면 둘 이상의 호출에 대해 서로 다른 값을 반환할 수 있습니다: thenReturn(value1, value2, value-n, …).
이 구문으로는 void 반환 메서드를 모의할 수 없다는 점에 유의하세요. 해당 경우 해당 방법의 확인을 사용합니다.
모의에 대한 호출을 확인하기 위해 Mockito.verify(mock).method(args)를 사용할 수 있으며 verifyNoMoreInteractions(mock)를 사용하여 모의에 더 이상 호출이 수행되지 않았는지 확인할 수도 있습니다.
인수를 확인하기 위해 특정 값을 전달하거나 any(), anyString() 및 anyInt()와 같은 사전 정의된 매처를 사용할 수 있습니다. 이러한 종류의 일치자가 훨씬 더 많으며 다음 예에서 볼 수 있는 일치자를 정의할 수도 있습니다.
@Test
public void assertTwoMethodsHaveBeenCalled() {
UserForm userForm = new UserForm();
userForm.username = "foo";
Mockito.when(loginService.login(userForm)).thenReturn(true);
String login = loginController.login(userForm);
Assert.assertEquals("OK", login);
Mockito.verify(loginService).login(userForm);
Mockito.verify(loginService).setCurrentUser("foo");
}
@Test
public void assertOnlyOneMethodHasBeenCalled() {
UserForm userForm = new UserForm();
userForm.username = "foo";
Mockito.when(loginService.login(userForm)).thenReturn(false);
String login = loginController.login(userForm);
Assert.assertEquals("KO", login);
Mockito.verify(loginService).login(userForm);
Mockito.verifyNoMoreInteractions(loginService);
}
모의 메서드 호출에 EasyMock.expect(mock.method(args)).andReturn(value) 을 사용할 수 있습니다. 또한 모의 호출을 확인하기 위해 EasyMock.verify(mock)를 사용할 수 있습니다. 이를 위해서는 EasyMock.replay(mock)를 호출한 후 항상 호출해야 합니다. 또한 인수를 확인하기 위해 특정 값을 전달할 수 있습니다. 또는 isA(Class.class), anyString(), anyInt()와 같은 사전 정의된 매처와 훨씬 더 많은 종류의 매처가 있고 다시 우리를 정의할 수 있는 가능성도 있습니다.
@Test
public void assertTwoMethodsHaveBeenCalled() {
UserForm userForm = new UserForm();
userForm.username = "foo";
EasyMock.expect(loginService.login(userForm)).andReturn(true);
loginService.setCurrentUser("foo");
EasyMock.replay(loginService);
String login = loginController.login(userForm);
Assert.assertEquals("OK", login);
EasyMock.verify(loginService);
}
@Test
public void assertOnlyOneMethodHasBeenCalled() {
UserForm userForm = new UserForm();
userForm.username = "foo";
EasyMock.expect(loginService.login(userForm)).andReturn(false);
EasyMock.replay(loginService);
String login = loginController.login(userForm);
Assert.assertEquals("KO", login);
EasyMock.verify(loginService);
}
JMockit을 사용하여 기록, 재생 및 확인이라는 테스트 단계를 정의했습니다.
기록(record)은 새로운 Expectations(){{}} 블록(여러 모의 개체에 대한 작업을 정의할 수 있음)에서 수행되고, 재생(reply)은 테스트된 클래스의 메서드(모의 개체를 호출해야 함)를 호출하여 간단히 수행되며, 확인(verification)은 다음과 같습니다. 새로운 Verifications(){{}} 블록 내에서 수행됩니다(여러 모의에 대한 검증을 정의할 수 있음).
모의 메서드 호출의 경우 Expectations 블록 내부에서 mock.method(args); result = value;를 사용할 수 있습니다. 여기에서는 result = value;대신 return(value1, value2, …, valuen);을 사용하여 둘 이상의 호출에 대해 서로 다른 값을 반환할 수 있습니다.
모의 호출을 확인(verify)하기 위해 새로운 Verifications(){{mock.call(value)}} 또는 new Verifications(mock){{}}을 사용하여 이전에 정의된 모든 예상 호출을 확인할 수 있습니다.
args를 확인(verify)하기 위해 특정 값을 전달할 수 있습니다. 또는 any, anyString, anyLong과 같은 사전 정의된 값과 훨씬 더 많은 종류의 특수 값이 있고 다시 매처(Hamcrest 매처여야 함)를 정의할 수 있습니다.
@Test
public void assertTwoMethodsHaveBeenCalled() {
UserForm userForm = new UserForm();
userForm.username = "foo";
new Expectations() {{
loginService.login(userForm); result = true;
loginService.setCurrentUser("foo");
}};
String login = loginController.login(userForm);
Assert.assertEquals("OK", login);
new FullVerifications(loginService) {};
}
@Test
public void assertOnlyOneMethodHasBeenCalled() {
UserForm userForm = new UserForm();
userForm.username = "foo";
new Expectations() {{
loginService.login(userForm); result = false;
// no expectation for setCurrentUser
}};
String login = loginController.login(userForm);
Assert.assertEquals("KO", login);
new FullVerifications(loginService) {};
}
Mockito는 Mockito.when(mock.method(args)) 다음에 .thenThrow(ExceptionClass.class)를 사용하여 예외를 발생시키는 모의 기능을 제공합니다.
@Test
public void mockExceptionThrowing() {
UserForm userForm = new UserForm();
Mockito.when(loginService.login(userForm)).thenThrow(IllegalArgumentException.class);
String login = loginController.login(userForm);
Assert.assertEquals("ERROR", login);
Mockito.verify(loginService).login(userForm);
Mockito.verifyNoInteractions(loginService);
}
EasyMock.expect(…) 호출 후에 .andThrow(new ExceptionClass())를 사용하여 예외 발생을 모의할 수 있습니다:
@Test
public void mockExceptionThrowing() {
UserForm userForm = new UserForm();
EasyMock.expect(loginService.login(userForm)).andThrow(new IllegalArgumentException());
EasyMock.replay(loginService);
String login = loginController.login(userForm);
Assert.assertEquals("ERROR", login);
EasyMock.verify(loginService);
}
JMockito로 예외를 던지는 것은 특히 쉽습니다. "정상적인" 반환 대신에 모의 메서드 호출의 결과로 예외를 반환하면 됩니다.
@Test
public void mockExceptionThrowing() {
UserForm userForm = new UserForm();
new Expectations() {{
loginService.login(userForm); result = new IllegalArgumentException();
// no expectation for setCurrentUser
}};
String login = loginController.login(userForm);
Assert.assertEquals("ERROR", login);
new FullVerifications(loginService) {};
}
메서드 호출에 대한 인수로 전달하기 위해 모의 개체를 만들 수도 있습니다. Mockito를 사용하면 한 줄로 이를 수행할 수 있습니다.
@Test
public void mockAnObjectToPassAround() {
UserForm userForm = Mockito.when(Mockito.mock(UserForm.class).getUsername())
.thenReturn("foo").getMock();
Mockito.when(loginService.login(userForm)).thenReturn(true);
String login = loginController.login(userForm);
Assert.assertEquals("OK", login);
Mockito.verify(loginService).login(userForm);
Mockito.verify(loginService).setCurrentUser("foo");
}
EasyMock.mock(Class.class)를 사용하여 인라인 모의 객체를 만들 수 있습니다. 그 후, EasyMock.expect(mock.method())를 사용하여 실행을 준비할 수 있으며, 사용하기 전에 항상 EasyMock.replay(mock)를 호출하는 것을 기억하세요.
@Test
public void mockAnObjectToPassAround() {
UserForm userForm = EasyMock.mock(UserForm.class);
EasyMock.expect(userForm.getUsername()).andReturn("foo");
EasyMock.expect(loginService.login(userForm)).andReturn(true);
loginService.setCurrentUser("foo");
EasyMock.replay(userForm);
EasyMock.replay(loginService);
String login = loginController.login(userForm);
Assert.assertEquals("OK", login);
EasyMock.verify(userForm);
EasyMock.verify(loginService);
}
하나의 메소드에 대해서만 객체를 모의하려면 모의된 객체를 테스트 메소드에 매개변수로 전달하기만 하면 됩니다. 그런 다음 다른 모형과 마찬가지로 기대치를 생성할 수 있습니다.
@Test
public void mockAnObjectToPassAround(@Mocked UserForm userForm) {
new Expectations() {{
userForm.getUsername(); result = "foo";
loginService.login(userForm); result = true;
loginService.setCurrentUser("foo");
}};
String login = loginController.login(userForm);
Assert.assertEquals("OK", login);
new FullVerifications(loginService) {};
new FullVerifications(userForm) {};
}
때때로 모의 호출에 대한 인수 일치는 고정 값이나 anyString()보다 조금 더 복잡해야 합니다. 이 경우 Mockito에는 argThat(ArgumentMatcher<>)와 함께 사용되는 matcher 클래스가 있습니다.
@Test
public void argumentMatching() {
UserForm userForm = new UserForm();
userForm.username = "foo";
// default matcher
Mockito.when(loginService.login(Mockito.any(UserForm.class))).thenReturn(true);
String login = loginController.login(userForm);
Assert.assertEquals("OK", login);
Mockito.verify(loginService).login(userForm);
// complex matcher
Mockito.verify(loginService).setCurrentUser(ArgumentMatchers.argThat(
new ArgumentMatcher<String>() {
@Override
public boolean matches(String argument) {
return argument.startsWith("foo");
}
}
));
}
사용자 정의 인수 일치는 실제 일치자를 생성한 다음 이를 EasyMock.reportMatcher(IArgumentMatcher)로 보고하는 정적 메서드를 생성해야 하기 때문에 EasyMock을 사용하면 조금 더 복잡합니다.
이 메서드가 생성되면 메서드 호출과 함께 모의 기대에 대해 이를 사용합니다(인라인 예제에서 볼 수 있음).
@Test
public void argumentMatching() {
UserForm userForm = new UserForm();
userForm.username = "foo";
// default matcher
EasyMock.expect(loginService.login(EasyMock.isA(UserForm.class))).andReturn(true);
// complex matcher
loginService.setCurrentUser(specificArgumentMatching("foo"));
EasyMock.replay(loginService);
String login = loginController.login(userForm);
Assert.assertEquals("OK", login);
EasyMock.verify(loginService);
}
private static String specificArgumentMatching(String expected) {
EasyMock.reportMatcher(new IArgumentMatcher() {
@Override
public boolean matches(Object argument) {
return argument instanceof String
&& ((String) argument).startsWith(expected);
}
@Override
public void appendTo(StringBuffer buffer) {
//NOOP
}
});
return null;
}
특별한 with(Delegate) 메소드를 사용하여 JMockit과 일치하는 사용자 정의 인수를 생성할 수 있습니다:
@Test
public void argumentMatching() {
UserForm userForm = new UserForm();
userForm.username = "foo";
// default matcher
new Expectations() {{
loginService.login((UserForm) any);
result = true;
// complex matcher
loginService.setCurrentUser(with(new Delegate<String>() {
@Override
public boolean matches(Object item) {
return item instanceof String && ((String) item).startsWith("foo");
}
}));
}};
String login = loginController.login(userForm);
Assert.assertEquals("OK", login);
new FullVerifications(loginService) {};
}
Mockito는 두 가지 방법으로 부분 모의(일부 메서드에서 모의 메서드 호출 대신 실제 구현을 사용하는 모의)를 허용합니다.
일반적인 모의 메서드 호출 정의에서 .thenCallRealMethod()를 사용할 수도 있고, 모의 대신 스파이를 생성할 수도 있습니다. 이 경우 기본 동작은 모의되지 않은 모든 메서드에서 실제 구현을 호출하는 것입니다.
@Test
public void partialMocking() {
// use partial mock
loginController.loginService = spiedLoginService;
UserForm userForm = new UserForm();
userForm.username = "foo";
// let service's login use implementation so let's mock DAO call
Mockito.when(loginDao.login(userForm)).thenReturn(1);
String login = loginController.login(userForm);
Assert.assertEquals("OK", login);
// verify mocked call
Mockito.verify(spiedLoginService).setCurrentUser("foo");
}
모의를 생성할 때 어떤 메서드를 조롱할지 정의해야 하기 때문에 부분적 조롱도 EasyMock을 사용하면 좀 더 복잡해집니다.
EasyMock.partialMockBuilder(Class.class).addMockedMethod(“methodName”).createMock()을 사용하여 이 작업을 수행할 수 있습니다. 그 후, 우리는 모의를 다른 비부분 모의로 사용할 수 있습니다:
@Test
public void partialMocking() {
UserForm userForm = new UserForm();
userForm.username = "foo";
// use partial mock
LoginService loginServicePartial = EasyMock.partialMockBuilder(LoginService.class)
.addMockedMethod("setCurrentUser").createMock();
loginServicePartial.setCurrentUser("foo");
// let service's login use implementation so let's mock DAO call
EasyMock.expect(loginDao.login(userForm)).andReturn(1);
loginServicePartial.setLoginDao(loginDao);
loginController.loginService = loginServicePartial;
EasyMock.replay(loginDao);
EasyMock.replay(loginServicePartial);
String login = loginController.login(userForm);
Assert.assertEquals("OK", login);
// verify mocked call
EasyMock.verify(loginServicePartial);
EasyMock.verify(loginDao);
}
JMockit을 사용한 부분 mocking은 특히 쉽습니다. Expectations(){{}}에 정의된 모의 동작이 없는 모든 메서드 호출은 "실제" 구현을 사용합니다.
이제 login() 메서드의 실제 구현을 사용하는 동안 setCurrentUser() 메서드를 모의하기 위해 LoginService 클래스를 부분적으로 모의하고 싶다고 가정해 보겠습니다.
이를 위해 먼저 LoginService 인스턴스를 생성하여 기대 블록에 전달합니다. 그런 다음 setCurrentUser() 메서드에 대한 기대값만 기록합니다.
@Test
public void partialMocking() {
LoginService partialLoginService = new LoginService();
partialLoginService.setLoginDao(loginDao);
loginController.loginService = partialLoginService;
UserForm userForm = new UserForm();
userForm.username = "foo";
new Expectations(partialLoginService) {{
// let's mock DAO call
loginDao.login(userForm); result = 1;
// no expectation for login method so that real implementation is used
// mock setCurrentUser call
partialLoginService.setCurrentUser("foo");
}};
String login = loginController.login(userForm);
Assert.assertEquals("OK", login);
// verify mocked call
new Verifications() {{
partialLoginService.setCurrentUser("foo");
}};
}