测试void方法的行为



假设我有以下服务对象

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,但与EasyMockMockito等类似。此外,您需要将用户名生成提取到类似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();
}

导线UserNameGeneratorUserDao相同。并将代码更改为:

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的默认实现,并将名称生成逻辑移到那里。

现在,您可以通过嘲笑UserNameGeneratorUserDao来轻松地检查行为。

用户名长度小于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;
}

有了它,您可以传递不同类型的用户名,并测试代码的行为。

相关内容

  • 没有找到相关文章