【问题标题】:ReflectionTestUtils - Setting field of type [null] on target objectReflectionTestUtils - 在目标对象上设置 [null] 类型的字段
【发布时间】:2023-04-10 21:42:01
【问题描述】:

我是单元测试和 Mockito 的新手。我正在尝试为我的 Dao 类编写测试:

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

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

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

【问题讨论】:

    标签: java unit-testing mockito


    【解决方案1】:

    您的代码存在多个问题:

    1. 您在属性中直接使用@Value,您将很难使用它为类设置测试。
    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());
        }
    }
    

    【讨论】:

    • With @RunWith(MockitoJUnitRunner.class 问题仍然存在。第二个选项抛出 failed to release mocks。看起来我有版本冲突。附上pom.xml 上面。
    • 我已经更新了我的答案。请检查它;)
    • 现在它确实有效,但我必须以某种方式更改功能,因为我想将我的查询存储在单个 .properties 文件中。
    • 如果我的回答帮助您考虑支持它甚至接受它作为答案,以便它可以“关闭”并且其他人可以从中受益并轻松理解这可能是类似问题的可能解决方案; ) 谢谢!
    猜你喜欢
    • 2018-08-26
    • 1970-01-01
    • 2019-07-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-10-14
    相关资源
    最近更新 更多