Reputation: 349
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
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
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
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
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
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
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