被測類往往依賴其他類(DAO、Service、外部 API)。若測試時引入真實依賴:
Mock 對象通過運行時動態代理(基於 JDK 反射),在測試中替換真實依賴,實現:
// POJO,無邏輯,可不 Mock
public class User {
private String name;
public User() {}
public User(String name) { this.name = name; }
// getter/setter
}
public interface UserDAO {
void saveUser(User user);
User getUser(Long id);
}
public class UserService {
private UserDAO userDAO;
public void setUserDAO(UserDAO userDAO) { this.userDAO = userDAO; }
public void registerUser(String name) {
User user = new User(name);
userDAO.saveUser(user);
}
public User findUser(Long id) {
return userDAO.getUser(id);
}
}
import org.jmock.Expectations;
import org.jmock.Mockery;
import org.junit.Test;
public class UserServiceTest {
private final Mockery context = new Mockery();
private final UserDAO mockDAO = context.mock(UserDAO.class);
private final UserService service = new UserService();
@Test
public void testRegisterUser_shouldSaveUser() {
// 設置期望:saveUser 被調用一次,參數 name="Alice"
context.checking(new Expectations() {{
oneOf(mockDAO).saveUser(with(any(User.class)));
}});
service.setUserDAO(mockDAO);
service.registerUser("Alice");
// 隱式驗證:context.assertIsSatisfied() 由 JUnit Runner 自動調用
}
@Test
public void testFindUser_shouldReturnMockedUser() {
final User expected = new User("Bob");
context.checking(new Expectations() {{
allowing(mockDAO).getUser(123L); will(returnValue(expected));
}});
service.setUserDAO(mockDAO);
User actual = service.findUser(123L);
assertSame(expected, actual);
}
}
| 語法 | 含義 |
|---|---|
context.mock(Interface.class) | 創建接口的 Mock 對象 |
oneOf(mock).method(args) | 期望方法被調用恰好一次 |
allowing(mock).method(args) | 允許方法被調用任意次數(存根模式) |
with(any(Class.class)) | 參數匹配器:接受該類型任意實例 |
will(returnValue(obj)) | 指定返回值 |
will(throwException(ex)) | 指定拋出異常 |
oneOf 驗證調用次數,避免過度寬鬆的 allowing 掩蓋 BugJMock 以聲明式期望替代手寫 Stub,讓隔離測試代碼可讀、可維護、聚焦業務邏輯。掌握「接口隔離、精確期望、行為驗證」三板斧,即可在 TDD 中暢行無阻,實現「先寫測試、後寫實現、依賴未就緒也能測」的工程理想。