Home > Software design >  ReflectionTestUtils - Setting field of type [null] on target object
ReflectionTestUtils - Setting field of type [null] on target object

Time:12-06

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:

  1. You are using @Value directly in a property and you will have a hard time setting up your test for the class with this.
  2. You are missing the enablement of Mockito annotations in your test.
  3. You are instantiating UserDaoImpl in your test method when you should be relying on the instance created by Mockito.
  4. You are also creating a NamedParameterJdbcTemplate and then using ReflectionTestUtils to wire it to the UserDaoImpl object.
  5. And you are mocking the wrong object. You need to mock the calls to template, not userDao.

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