测试void方法的行为

时间:2012-01-11 15:23:57

标签: java unit-testing junit mockito

假设我有以下服务对象

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);
    }
}

我想测试方法的行为&#34; addUser&#34;当用户名长度小于8且用户名大于8个字符时。如何在单元测试UserService.addUser(...)中进行验证并验证它?我知道使用assert(),但值&#34;密码&#34;在addUser(...)方法之外不可用。

我使用JUnit和Mockito。

6 个答案:

答案 0 :(得分:7)

我提出了一个解决方案,几个月后再次重新访问问题。

我们的想法是观察传递给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);
    }
}

Guillaume实际上有最接近的答案,但他回答使用jMock。但是,他给了我关于如何实现这个目标的想法,所以我认为他也应该得到一些荣誉。

答案 1 :(得分:1)

您正在测试副作用,但幸运的是,您需要的所有内容都会传递给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模仿Dao。

答案 2 :(得分:1)

使用模拟框架。以下示例使用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());
}

当然,根据您的“随机”方法,您可能也想测试其特定行为。除此之外,上述内容为您的代码提供了很好的覆盖范围。

答案 3 :(得分:0)

这完全取决于你的DAO的保存方法是如何实现的。

如果您实际存储到硬编码存储库,那么您可能需要查询存储库本身以获取您所参与的值。

如果你有一个被调用的底层接口,那么你应该能够设置一个回调方法并检索正在保存的实际值。

我从未使用过Mockito所以我无法给出确切的代码,本文应该解决这个问题:

Using Mockito, how do I intercept a callback object on a void method?

答案 4 :(得分:0)

考虑将用户名生成逻辑从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的默认实现,并在那里移动名称生成逻辑。

现在,您可以通过模拟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));

答案 5 :(得分:-1)

最简单的方法是提取具有用户名更正逻辑的部分

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;
}

通过这种方式,您可以传递不同类型的用户名并测试代码的行为。