8

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.

bric3
  • 40,072
  • 9
  • 91
  • 111
Ggg
  • 249
  • 1
  • 7
  • 18

6 Answers6

7

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.

Ggg
  • 249
  • 1
  • 7
  • 18
1

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.

Matthew Farwell
  • 60,889
  • 18
  • 128
  • 171
  • This is not a Mock, this is a Stub. Check that: http://martinfowler.com/articles/mocksArentStubs.html – Guillaume Jan 11 '12 at 15:48
  • Yeah, I know, but I was using the same terminology as the OP. I've changed the text. – Matthew Farwell Jan 11 '12 at 15:59
  • I have tried this approach, using Mockito to mock UserDao object. Unfortunately, ReflectionTestUtils always return a null userDao object. Have done something wrong? – Ggg Jan 16 '12 at 02:48
1

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.

Guillaume
  • 22,694
  • 14
  • 56
  • 70
0

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?

Community
  • 1
  • 1
0

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));
Mairbek Khadikov
  • 7,939
  • 3
  • 35
  • 51
-1

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.

derdo
  • 1,036
  • 12
  • 21
  • I thought about this, but the actual case is composed by several method already. What I've written here is the more simplified version of the actual case. – Ggg Jan 12 '12 at 01:21
  • Despite the neg. vote I still think it will make things simpler to test the username validation and modification logic in isolation (separate from testing what gets passed into save method). Even more so, if that logic has other complexities. And I also think you won't need to use any mocking for testing that logic. – derdo Jan 12 '12 at 20:26