I am new with Unit testing and Mockito. I'm trying to write tests for my Dao class:
@Repository
@NoArgsConstructor
public class UserDaoImpl implements UserDao {
private NamedParameterJdbcTemplate template;
@Value("${users.find.by_id}")
private String findByIdQuery;
private RowMapper<User> rowMapper = (rs, rowNum) -> {
User user = new User();
user.setId(rs.getInt("id"));
user.setFirstName(rs.getString("firstname"));
user.setLastName(rs.getString("lastname"));
user.setEmail(rs.getString("email"));
user.setPassword(rs.getString("password"));
user.setEnabled(rs.getBoolean("enabled"));
return user;
};
public UserDaoImpl(NamedParameterJdbcTemplate template) {
super();
this.template = template;
}
@Override
public Optional<User> findById(int id) {
SqlParameterSource param = new MapSqlParameterSource("id", id);
User user = null;
try {
user = template.queryForObject(findByIdQuery, param, BeanPropertyRowMapper.newInstance(User.class));
} catch (DataAccessException ex) {
ex.printStackTrace();
}
return Optional.ofNullable(user);
}
}
In my simple test I simply added @Mock anotation for my NamedParameterJdbcTemplate, and trying to put it into the UserDaoImpl:
public class UserDaoTest {
@Mock
public NamedParameterJdbcTemplate template;
@InjectMocks
public UserDao userDao;
@Test
public void findByIdTest() {
template = new NamedParameterJdbcTemplate(new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.addScript("classpath:db/schema.sql")
.addScript("classpath:db/test-data.sql")
.build());
userDao = new UserDaoImpl();
ReflectionTestUtils.setField(userDao, "template", template);
Mockito.when(userDao.findById(1).get().getEmail()).thenReturn("[email protected]");
User user = userDao.findById(1).get();
assertNotNull(user);
assertEquals("[email protected]", user.getEmail());
}
}
Each time I run the test, I get java.lang.NullPointerException for the field template. Cant find what is the correct way of implementing the test.
Here is my pom.xml:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<java.version>11</java.version>
</properties>
...
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.liquibase</groupId>
<artifactId>liquibase-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
CodePudding user response:
You have multiple problems with your code:
- You are using
@Value
directly in a property and you will have a hard time setting up your test for the class with this. - You are missing the enablement of Mockito annotations in your test.
- You are instantiating
UserDaoImpl
in your test method when you should be relying on the instance created by Mockito. - You are also creating a
NamedParameterJdbcTemplate
and then usingReflectionTestUtils
to wire it to theUserDaoImpl
object. - And you are mocking the wrong object. You need to mock the calls to
template
, notuserDao
.
To tackle the first one you need to change UserDaoImpl
as follows:
@Repository
@NoArgsConstructor
public class UserDaoImpl implements UserDao {
private NamedParameterJdbcTemplate template;
private String findByIdQuery;
private RowMapper<User> rowMapper = (rs, rowNum) -> {
User user = new User();
user.setId(rs.getInt("id"));
user.setFirstName(rs.getString("firstname"));
user.setLastName(rs.getString("lastname"));
user.setEmail(rs.getString("email"));
user.setPassword(rs.getString("password"));
user.setEnabled(rs.getBoolean("enabled"));
return user;
};
public UserDaoImpl(NamedParameterJdbcTemplate template, @Value("${users.find.by_id}") String findByIdQuery) {
super();
this.template = template;
this.findByIdQuery = findByIdQuery;
}
@Override
public Optional<User> findById(int id) {
SqlParameterSource param = new MapSqlParameterSource("id", id);
User user = null;
try {
user = template.queryForObject(findByIdQuery, param, BeanPropertyRowMapper.newInstance(User.class));
} catch (DataAccessException ex) {
ex.printStackTrace();
}
return Optional.ofNullable(user);
}
}
To tackle 2., 3., 4. and 5. you need to enable Mockito annotations programmatically and remove userDao = new UserDaoImpl();
line and also the template
variable from the method test as follows:
@RunWith(MockitoJUnitRunner.class)
public class UserDaoTest {
@Mock
public NamedParameterJdbcTemplate template;
public UserDao userDao;
@Before
public void init() {
MockitoAnnotations.initMocks(this);
userDao = new UserDaoImpl(template, "query-string");
}
@Test
public void findByIdTest() {
// Arrange
User user = new User();
user.setId(rs.getInt("id"));
user.setFirstName(rs.getString("firstname"));
user.setLastName(rs.getString("lastname"));
user.setEmail(rs.getString("[email protected]"));
user.setPassword(rs.getString("password"));
user.setEnabled(rs.getBoolean("enabled"));
Mockito.when(template.queryForObject(anyString(), any(SqlParameterSource.class), any(RowMapper.class))).thenReturn(user);
template.queryForObject(findByIdQuery, param, BeanPropertyRowMapper.newInstance(User.class));
// Act
User user = userDao.findById(1).get();
// Assert
assertNotNull(user);
assertEquals("[email protected]", user.getEmail());
}
}