ReflectionTestUtils - 在目标对象上设置 [null] 类型的字段

ReflectionTestUtils - Setting field of type [null] on target object

我是单元测试和 Mockito 的新手。我正在尝试为我的 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);
    }
}

在我的简单测试中,我只是为我的 NamedParameterJdbcTemplate 添加了 @Mock 注释,并尝试将其放入 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("Keanu@gmail.com");
        
        User user = userDao.findById(1).get();
        assertNotNull(user);
        assertEquals("Keanu@gmail.com", user.getEmail());
    }
}

每次我 运行 测试时,我都会得到 java.lang.NullPointerException 字段 template。无法找到实施测试的正确方法。

这是我的 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>

您的代码有多个问题:

  1. 您直接在 属性 中使用 @Value,您将很难用它来设置 class 的测试。
  2. 您缺少在测试中启用 Mockito 注释。
  3. 当您应该依赖 Mockito 创建的实例时,您正在测试方法中实例化 UserDaoImpl
  4. 您还创建了一个 NamedParameterJdbcTemplate,然后使用 ReflectionTestUtils 将其连接到 UserDaoImpl 对象。
  5. 而且你在嘲笑错误的对象。您需要模拟对 template 的调用,而不是 userDao.

要解决第一个问题,您需要按如下方式更改 UserDaoImpl

@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);
    }
}

要解决 2.、3.、4. 和 5.,您需要以编程方式启用 Mockito 注释并从方法测试中删除 userDao = new UserDaoImpl(); 行和 template 变量,如下所示:

@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("Keanu@gmail.com"));
        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("Keanu@gmail.com", user.getEmail());
    }
}