假设我有以下服务对象
public class UserService {
@Autowired
private UserDao dao;
public void addUser(String username, String password) {
if (username.length() < 8 ) {
username = username + "random" ; // add some random string
}
User user = new User(username, password);
dao.save(user);
}
}
我想测试方法"addUser"在用户名长度小于8和用户名大于8个字符时的行为。如何在单元测试中使用方法UserService.addUser(…),并对其进行验证?我知道使用assert(),但值"password"在addUser(…)方法之外不可用。
我用JUnit和Mockito。
经过几个月的再次访问,我找到了一个解决方案。
其思想是观察传递给UserDao的对象用户。我们可以通过这样做来检查用户名的值,因此单元测试代码为:
@RunWith(MockitoJUnitRunner.class)
public class UserServiceTest {
@Mock
private UserDao dao;
@InjectMock
private UserService service;
@Test
public void testAddingUserWithLessThan8CharUsername () {
final String username = "some";
final String password = "user";
doAnswer(new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
Object[] args = invocationOnMock.getArguments();
User toBeSaved = (User) args[0];
Assert.assertEquals(username + "random", toBeSaved.getPassword());
return null;
}
}).when(userDao).save(Matchers.any(User.class));
service.addUser(username, password);
}
}
纪尧姆实际上有最接近的答案,但他用jMock回答。然而,他给了我如何实现这一目标的想法,所以我认为他也应该得到一些赞扬。
您正在测试副作用,但幸运的是,您所需要的一切都被传递到了dao.save()。首先,创建一个UserDao(带或不带Mockito),然后您可以使用ReflectionTestUtils在UserService中设置dao,然后您就可以测试传递到dao.save()的值。
类似于:
private class TestUserDao extends UserDao {
private User savedUser;
public void save(User user) {
this.savedUser = user;
}
}
@Test public void testMethod() {
UserService userService = new UserService();
TestUserDao userDao = new TestUserDao();
ReflectionTestUtils.setField(userService, "dao", userDao);
userService.addUser("foo", "bar");
assertEquals("foo", userDao.savedUser.username.substring(0, 3));
assertEquals("bar", userDao.savedUser.password);
}
或者,如果你愿意,你可以使用Mockito来模拟刀。
使用一个mocking框架。下面的示例使用JMock2
,但与EasyMock
、Mockito
等类似。此外,您需要将用户名生成提取到类似UsernameGenmerator
的东西中才能模拟它。您需要对用户名生成器进行另一个特定的测试。
private final Mockery mockery = new Mockery();
private final UserDao mockDao = mockery.mock(UserDao.class);
private final UsernameGenerator mockUserNameGenerator = mockery.mock(UsernameGenerator.class);
@Test
public void addUserUsesDaoToSaveUser() {
final String username = "something";
final String generatedUsername = "siomething else";
final String password = "a password";
mockery.checking(new Expectations() {{
oneOf(mockUsernameGenerator).generateUsername(username);
will(returnValue(generatedUsername));
oneOf(mockDao).save(new User(generatedUsername, password)); // assumes your User class has a "natueral" equals/hashcode
}});
UserService userService = new UserService();
userService.addUser(username, password);
}
对于UsernameGenerator
,您需要测试返回用户名的长度:
@Test
public void leavesUsernameUnchangedIfMoreThanEightChars() {
final String username = "123456789";
final UsernameGenerator usernameGenerator = new UsernameGenerator();
assertEquals(username, userGenerator.generateUsername(username));
}
@Test
public void addsCharactersToUsernameIfLessThanEightChars() {
final String username = "1234567";
final UsernameGenerator usernameGenerator = new UsernameGenerator();
assertEquals(8, userGenerator.generateUsername(username).length());
}
当然,根据你的"随机"方法,你可能也想测试它的具体行为。除此之外,以上内容为您的代码提供了充分的覆盖范围。
这一切都取决于DAO的保存方法是如何实现的。
如果您实际存储到一个硬编码的存储库中,那么您可能需要查询存储库本身,以查找您所参与的值。
如果您有一个被调用的底层接口,那么您应该能够设置一个回调方法并检索正在保存的实际值。
我从来没有使用过Mockito,所以我不能给你确切的代码,这篇文章应该解决这个问题:
使用Mockito,如何拦截void方法上的回调对象?
考虑从UserService中提取用户名生成逻辑作为依赖项。
interface UserNameGenerator {
Strign generate();
}
导线UserNameGenerator
与UserDao
相同。并将代码更改为:
public class UserService {
@Autowired
private UserDao dao;
@Autowired
private UserNameGenerator nameGenerator;
public void addUser(String username, String password) {
if (username.length() < 8 ) {
username = nameGenerator.generate();
}
User user = new User(username, password);
dao.save(user);
}
}
接下来创建UserNameGenerator
的默认实现,并将名称生成逻辑移到那里。
现在,您可以通过嘲笑UserNameGenerator
和UserDao
来轻松地检查行为。
当用户名长度小于8时检查用例
String username = "123";
String password = "pass";
String generatedName = "random";
// stub generator
when(nameGenerator.generate()).thenReture(generatedName);
// call the method
userService.addUser(username, password);
// verify that generator was called
verify(nameGenerator).generate();
verify(userDao).save(new User(generatedName, password));
当用户名长度大于8时检查用例
String username = "123456789";
String password = "pass";
String generatedName = "random";
// call the method
userService.addUser(username, password);
// verify that generator was never called
verify(nameGenerator, never()).generate();
verify(userDao).save(new User(username, password));
最简单的方法是提取具有用户名校正逻辑的部分
if (username.length() < 8 ) {
username = username + "random" ; // add some random string
}
并测试该方法的返回值。
public string GetValidUsername(string userName){
if (username.length() < 8 ) {
return username + "random" ; // add some random string
}
return username;
}
有了它,您可以传递不同类型的用户名,并测试代码的行为。