Gama
Gama

Reputation: 349

Testing the behavior of void method

Suppose I have the following service object

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

I want to test the behaviour of the method "addUser" when username length is less 8 and when the username is more than 8 char. How do approach in unit test UserService.addUser(...), and verify it? I am aware using assert(), but the value "password" is not available outside the addUser(...) method.

I use JUnit and Mockito.

Upvotes: 8

Views: 5261

Answers (6)

Gama
Gama

Reputation: 349

I came up a solution, after some re-visit the problem again after some months.

The idea is to observed the object user that is being passed to UserDao. We can inspect the value of the username by doing this, hence the unit test code:

@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 actually had the closest answer, but he answered using jMock. However, he gave me the idea on how to accomplish this, so I think he deserves some credit too.

Upvotes: 7

Guillaume
Guillaume

Reputation: 22822

Use a mocking framework. The example below uses JMock2, but it would be similar with EasyMock, Mockito, etc. Also, you need to extract the username generation to something like UsernameGenmerator to be able to mock it. You need another specific test for the username generator.

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

And for UsernameGenerator you need test on length of the returned username:

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

Of course, depending on your "random" method, you may want to test its specific behaviour too. Apart from that, the above provide sifficient coverage for your code.

Upvotes: 1

Mairbek Khadikov
Mairbek Khadikov

Reputation: 8089

Consider extracting user name generation logic as dependency from UserService.

interface UserNameGenerator {
    Strign generate();
}

Wire UserNameGenerator same as UserDao. And change the code to:

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

Next create the default implementation of UserNameGenerator and move name generating logic there.

Now you can easily check behavior by mocking UserNameGenerator and UserDao.

To check use case when username is length is less than 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));

To check use case when username is length is greater than 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));

Upvotes: 0

Matthew Farwell
Matthew Farwell

Reputation: 61695

You are testing side-effects, but fortunately, everything you need is passed to the dao.save(). First, create a UserDao (either with or without Mockito), then you can use ReflectionTestUtils to set the dao in the UserService, then you can test the values which are passed to dao.save().

Something like:

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

Or you can user Mockito to mock out the Dao if you want.

Upvotes: 1

Derek Van Cuyk
Derek Van Cuyk

Reputation: 1

It would all depend on how your DAO's save method is implemented.

If you are actually storing to a hard-coded repository, then you will probably need to query the repository itself for the values you are intereseted in.

If you have an underlying interface which is called, then you should be able to set up a callback method and retrieve the actual value which is being saved.

I have never used Mockito so I couldn't give you exact code which does this article should address that:

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

Upvotes: 0

derdo
derdo

Reputation: 1036

Easiest way is to extract the part where you have the user name correction logic

if (username.length() < 8 ) {
    username = username  + "random" ; // add some random string
}

into a method and test the return value of that method.

public string GetValidUsername(string userName){
    if (username.length() < 8 ) {
        return username  + "random" ; // add some random string
    }
    return username;
}

with this you can pass different types of username and test the behavior of your code.

Upvotes: -1

Related Questions