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.
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.
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.
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.
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?
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));
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.
Related
I'm trying to perform unit testing and write a test for jdbctemplate.query. I need the code coverage for the below method.
Code:
` public List<User> UserInfo(final Long runId){
jdbcTemplate.setFetchSize(10);
List<User> userList = jdbcTemplate.query(con -> con.prepareStatement(GET_USER_INFO)
, (rs, rowNum) -> {
String userName = rs.getString("username");
String shoppingItems = rs.getString("shopingItems");
Long shoppingid = rs.getLong("shoppingid");
User user= new User();
user.setCmts(userName);
user.setNodeid(shoppingItems);
user.setNodename(shoppingid);
user.setRunId(runId);
return user;
});
return userList;
}`
I have written the test as below, but running the test "coverage as junit" doesn't show me any code coverage for method which is inside the (rs, rowNum) -> {
Test:
`#InjectMocks
private OracleRepository oracleRepository;
#Mock
private JdbcTemplate jdbcTemplate;
#Mock
private ResultSet rs;
#Mock
private Connection connection;
#Mock
private PreparedStatement stmt;
#Mock
private RowMapper rowMapper;
#Test
public void UserInfoTest() throws SQLException {
String username= "joe";
String shoppingItems= "3";
long shoppingId = 1123456778;
long runId = 2;
List<User> listUser = new ArrayList<User>();
Mockito.when(rs.getString("cmts")).thenReturn(cmts);
Mockito.when(rs.getString("node")).thenReturn(node);
Mockito.when(rs.getLong("nodeid")).thenReturn(nodeid);
User user= new User();
user.setUserName(username);
user.setShoppingItems(shoppingItems);
user.setShoppingId(shoppingId);
user.setRunId(runId);
listUser.add(user);
Mockito.when(connection.prepareStatement(Mockito.any(String.class))).thenReturn(stmt);
Mockito.when(rowMapper.mapRow(Mockito.any(ResultSet.class), Mockito.any(Integer.class))).thenReturn(user);
Mockito.when(jdbcTemplate.query(Mockito.anyString(), Mockito.any(RowMapper.class))).thenReturn(listUser);
List<User> list = oracleRepository.UserInfo(runId);
}`
How do I solve this problem?
At the bottom of your test after the List<User> you can add:
ArgumentCaptor<RowMapper> argCaptor = ArgumentCaptor.forClass(RowMapper.class);
Mockito.verify(jdbcTemplate).query(Mockito.any(PreparedStatementCreator.class), argCaptor.capture());
RowMapper<User> rowMapper = argCaptor.getValue();
User userResult = rowMapper.mapRow(rs, -1);
verify(rs).getString("username");
verify(rs).getString("shopingItems");
verify(rs).getLong("shoppingid2");
assertEqual(cmts, userResult.getCmts());
This captures the functional interface you defined, then you call it as the action to test it. I checked the verifies and assertion also. However, this is not the easiest to understand if anyone else ever has to maintain this codebase. You might want to consider creating a class that implements the interface and test that directly for readability.
I've been learning about tests lately but this is the first test were I've had to pass a variable in a function that I'm mocking. I've written a similar test were the only difference is that i use an ArgumentMatcher in this test because the testInput.validate() needs 3 Strings to pass with it. I don't know this stuff very well so I'm sorry if the terminology is off.
Here is the code i'm trying to test:
#Component
public class RequestHandler {
private static Gson gson = new Gson();
private final UserRepository userRepository;
private final TestInput testInput;
#Autowired
public RequestHandler(UserRepository userRepository, TestInput testInput) {
this.userRepository = UserRepository;
this.testInput = testInput;
}
public String addUser(String username, String email, String password) {
if (testInput.validate(username, email, password) && !(userRepository.findById(email).isPresent())) {
User user = new User(username, email, password);
userRepository.save(user);
return gson.toJson(user);
} else {
return gson.toJson("error");
}
}
}
And here is my test:
public class RequestHandlerTest {
UserRepository userRepository = Mockito.mock(UserRepository.class);
TestInput testInput = Mockito.mock(TestInput.class);
RequestHandler requestHandler = new RequestHandler(userRepository, testInput);
String test = ArgumentMatchers.anyString();
#Test
public void addUserTest() {
Mockito.when(testInput.validate(test, test, test)).thenReturn(true, false);
Mockito.when(userRepository.findById(test).isPresent()).thenReturn(false, true);
String jsonUser = new Gson().toJson(new User("username123","example#mail.com","12344321"));
String jsonError = new Gson().toJson("error");
System.out.println("addUser Test1");
assertEquals(jsonUser, requestHandler.addUser("username123","example#mail.com","12344321"));
System.out.println("addUser Test2");
assertEquals(jsonError, requestHandler.addUser("username123","example#mail.com","12344321"));
}
}
I had a bunch of errors with this code and when I changed the ArgumentMatchers.anyString() to just ArgumentMatchers.any() I had 1 error instead of like 5.
I looked into the source code of this problem, and for information to other readers, the underlying problem was that the mocked function was declared "static". This is not evident from the posted problem.
There are many issues in your test.
You cannot use like this
String test = ArgumentMatchers.anyString();
Mockito.when(testInput.validate(test, test, test)).thenReturn(true, false);
You can clearly see from the error message what Mockito is saying when you do this
org.mockito.exceptions.misusing.InvalidUseOfMatchersException:
Invalid use of argument matchers!
3 matchers expected, 1 recorded:
which means you need to pass three different instances.
This line is also not correct
Mockito.when(userRepository.findById(test).isPresent()).thenReturn(false, true);
findById should return Optional, but you are returning boolean. When you use Mockito, you should mock individual steps. What I mean is in your example you need to mock userRepository.findById(test) and then isPresent on that returned mock. You cannot skip one step and go to the next.
Here is a working code
public class RequestHandlerTest {
UserRepository userRepository = Mockito.mock(UserRepository.class);
TestInput testInput = Mockito.mock(TestInput.class);
RequestHandler requestHandler = new RequestHandler(userRepository, testInput);
#Test
public void addUserTest() {
when(testInput.validate(anyString(), anyString(), anyString())).thenReturn(true, false);
User username123 = new User("username123", "example#mail.com", "12344321");
String jsonUser = new Gson().toJson(username123);
String jsonError = new Gson().toJson("error");
when(userRepository.findById(anyString())).thenReturn(Optional.empty(),Optional.of(username123));
System.out.println("addUser Test1");
assertEquals(jsonUser, requestHandler.addUser("username123","example#mail.com","12344321"));
System.out.println("addUser Test2");
assertEquals(jsonError, requestHandler.addUser("username123","example#mail.com","12344321"));
}
}
I have service:
#Slf4j
#Service
public class CashierServiceDefault implements CashierService {
private final UserRepository userRepository;
#Autowired
public CashierServiceDefault(UserRepository userRepository) {
this.userRepository = userRepository;
}
#Override
#Transactional
public CashierDto login(CashierDto cashier) {
User dbUser = userRepository.findOneByLoginAndPassword(cashier.getLogin(), cashier.getPassword());
validateCashier(cashier.getLogin(), dbUser);
User userWithToken = createAuthToken(dbUser);
return domainUserToCashierDto(userWithToken, cashier);
}
private void validateCashier(String login, User dbUser) {
if (dbUser == null) {
log.error("Cashier: {} not found", login);
throw new AuthException(AuthException.ErrorCode.USER_NOT_FOUND_EXCEPTION);
}
UserRole userRole = UserRole.valueOf(dbUser.getUserRole().getCode());
if (userRole != UserRole.CASHIER) {
log.error("User: {} has role: {}. expected: CASHIER ", login, userRole.toString());
throw new AuthException(AuthException.ErrorCode.USER_ROLE_NOT_PERMISSION_EXCEPTION);
}
}
private User createAuthToken(User user) {
user.setAuthToken(TokenGenerator.nextToken());
user.setAuthTokenCreatedDate(new Date());
return userRepository.save(user);
}
private CashierDto domainUserToCashierDto(User user, CashierDto cashier) {
//mapping user's fields to CashierDto,s fields
return cashier;
}
I want create Test for this service. I tried this:
#RunWith(SpringRunner.class)
public class CashierServiceDefaultTest {
#MockBean
private UserRepository userRepository;
private CashierService cashierService;
#Before
public void setUp() throws Exception {
cashierService = new CashierServiceDefault(userRepository);
}
#Test
public void login() {
CashierDto cashierDto = new CashierDto();
cashierDto.setLogin("Alex");
cashierDto.setPassword("123");
User user = new User();
user.setLogin("Alex");
user.setPassword("123");
//and other test values
when(userRepository.findOneByLoginAndPassword(cashierDto.getLogin(), cashierDto.getPassword())).thenReturn(user);
CashierDto found = cashierService.login(cashierDto);
assertThat(found.getAuthToken()).isEqualTo("123");
}
And I have questions:
1. How can I tests private methods in my service? Do I need to test them? If so, how?
2. How should I test the public login method? I made a stub for repository methods:
when(userRepository.findOneByLoginAndPassword(cashierDto.getLogin(), cashierDto.getPassword())).thenReturn(user);
But should I do stubs for internal service methods?(validateCashier, createAuthToken, domainUserToCashierDto). If so, how?
UnitTests do not test code, they verify public observable behavior which is return values and communication with dependencies.
private methods are implementation details which you test indirectly (as stated by JWo)
The reason is that you later may change your implementation details (refactor them) whithout breaking any of your existing UnitTests.
I would not test them directly. Since you implemented them as a help for some other methods, you can test those. By testing all public methods you will test the private ones, too. Don't forget to add inputs and outputs for the private methods, when testing the public ones.
Another way is to put test methods into the same package as the production code. Then you have to set your private methods to package or protected.
I've the following model:
public class Users {
public static PlayJongo jongo = Play.current().injector().instanceOf(PlayJongo.class);
public static MongoCollection users() {
return jongo.getCollection("DB.users");
}
..
..
public static Users authenticate(String email, String password) {
Users user = users().findOne("{email: #, removed: false}", email).as(Users.class);
if (user != null) {
if (HomeController.checkPassword(password, user.password)) {
return user;
}
}
return null;
}
..
I use that in my controllers as:
public Result authenticate() {
DynamicForm requestData = Form.form().bindFromRequest();
String email = requestData.get("email").trim();
String password = requestData.get("password").trim();
Users user = Users.authenticate(email, password);
if (user == null) {
flash("danger", "Incorrect email or password.");
return redirect(routes.HomeController.login());
}
session("email", user.getEmail());
session("role", user.getRole());
session("fullname", user.getLastname() + " " + user.getFirstname());
session("id", user.getId().toString());
return redirect(routes.HomeController.index());
}
I tried a lot of combination to use injection with play-jongo without result. E.g.
#Inject
public PlayJongo jongo;
public MongoCollection users() {
return jongo.getCollection("DocBox.users");
}
I enter in a loop of static/non-static referenced context errors. If I remove all static declaration, I'm unable to call Users.method. If I try to inject Users to a controller
public class HomeController extends Controller {
#Inject
public Users users;
.
.
and try to call a Users method:
Users user = users.authenticate(email, password);
I receive a org.jongo.marshall.MarshallingException.
My brain is definitively goes overheating, someone can explain me how to use Injection with play-jongo?
I solve the problem. Now I've a UsersRepository that contains the methods that operate on the mongo collection (authenticate, addUser, et al.). And a Users object that only contains the actual data fields (firstname, lastname, email, etc.).
After that I can inject UsersRepository into my controller and use that one instance everywhere.
Thanks to Greg Methvin, Tech Lead - Play Framework
I have a this class
public class AuthenticationModule {
String userName = "foo";
String password = "bar";
public void setUserName(String userName) {
this.userName = userName;
}
public void setPassword(String password ) {
this.password = password ;
}
AuthenticationServicePort authenticationServicePort;
AuthenticationService port;
private boolean authenicate(String userName, String password) {
authenticationServicePort = new AuthenticationServicePort();
port = authenticationServicePort.getAuthenticationServiceProxy();
return port.login(userName, password);
}
public boolean validateUser() {
return authenicate(userName, password);
}
}
and AuthenticationServicePort returns a WSDL port
I want to create a simple test case with a Mock AuthenticationServicePort which will return a 'true/false' value
How do I inject in my own MockObject without changing the java code?
Or worse case scenario, what is the easiest way to change to be be more easily testable.
You should avoid creating instances of classes which have any logic inside (not plain DTO objects). Instead you should design your classes in such a way that dependency injection container can build up complete graph of objects. In your code you need to answer yourself if each call of authenicate method does need a new instance of AuthenticationServicePort? If yes then you should use a factory pattern to create instances of this object and this factory should be injected (provided in constructor) so you can mock it and everything it will produce. If many calls of authenticate method can reuse same instance of AuthenticationServicePort then simply inject it (provide in constructor) and in your test provide mock instead of real implementation.
Here is an example test where AuthenticationServicePort is mocked, using JMockit 1.13:
public class AuthenticationModuleTest
{
#Tested AuthenticationModule authentication;
#Mocked AuthenticationServicePort authenticationService;
#Mocked AuthenticationService port;
#Test
public void validateUser()
{
final String userName = "tester";
final String password = "12345";
authentication.setUserName(userName);
authentication.setPassword(password);
new Expectations() {{ port.login(userName, password); result = true; }};
boolean validated = authentication.validateUser();
assertTrue(validated);
}
}