You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Java单元测试因KeyHolder返回null失败,模拟对象未生效

单元测试中KeyHolder无法返回有效对象导致断言失败

问题描述

我正在为某应用编写新的单元测试,目前遇到瓶颈:KeyHolder无法返回有效对象,导致测试断言失败。我已在测试中添加日志,确认keyHolder已设置正确值,但defaultDao.save()似乎未使用测试中定义的keyHolder。我预期断言会通过,因为已将测试中的KeyHolder硬编码为1。

相关代码

ScriptDao类

@Repository
public class ScriptDao extends DefaultDao {

    private static final Logger LOG = LoggerFactory.getLogger(ScriptDao.class);

    public Long save(ScriptModel script) {
        LOG.debug("saving script {}", script);

        String sql = "INSERT INTO " + DBTable.SCRIPT + " (script_type_id, hash, content) " +
                "VALUES(?, ?, ?)";

        List<Object> queryParams = new LinkedList<>();
        queryParams.add(script.getScriptType());
        queryParams.add(script.getHash());
        queryParams.add(script.getContent());

        KeyHolder keyHolder = save(sql, queryParams);

        return keyHolder.getKey() != null ? keyHolder.getKey().longValue() : null;
    }
}

DefaultDao类

@Repository
public class DefaultDao {

    @Autowired
    protected ObjectMapper jacksonObjectMapper;

    @Autowired
    protected JdbcTemplate jdbcTemplate;

    public KeyHolder save(String sql, List queryParams) {
        Object[] parameters = queryParams.toArray();
        printSQL(sql);
        printParams(queryParams);
        KeyHolder keyHolder = new GeneratedKeyHolder();
        int rowAffected = jdbcTemplate.update(getPreparedStatementCreator(sql, parameters), keyHolder);
        getLogger().debug("operation successfully completed on {} rows", rowAffected);
        return keyHolder;
    }
}

测试用例

@ExtendWith(MockitoExtension.class)
@MockitoSettings(strictness = Strictness.LENIENT)
public class ScriptDaoTest {

    @InjectMocks
    private ScriptDao scriptDao;

    @Mock
    private JdbcTemplate jdbcTemplate;

    @Mock
    private SqlRowSet sqlRowSet;

    @Mock
    private DefaultDao defaultDao;

    @Test
    void testSaveMethod() {
        ScriptModel script = new ScriptModel();
        script.setScriptType(1);
        script.setHash("hash123");
        script.setContent("content");

        Map<String, Object> keyMap = new HashMap<String, Object>();
        keyMap.put("", 1L);

        KeyHolder keyHolder = new GeneratedKeyHolder();
        List<Map<String, Object>> generatedKeys = keyHolder.getKeyList();
        generatedKeys.clear();
        generatedKeys.add(keyMap);

        when(defaultDao.save(anyString(), anyList())).thenReturn(keyHolder);

        Long result = scriptDao.save(script);

        assertEquals(1L, result);
    }
}

报错信息

org.opentest4j.AssertionFailedError: 
Expected :1
Actual   :null
<Click to see difference>


    at org.junit.jupiter.api.AssertionFailureBuilder.build(AssertionFailureBuilder.java:151)
    at org.junit.jupiter.api.AssertionFailureBuilder.buildAndThrow(AssertionFailureBuilder.java:132)
    at org.junit.jupiter.api.AssertEquals.failNotEqual(AssertEquals.java:197)
    at org.junit.jupiter.api.AssertEquals.assertEquals(AssertEquals.java:182)
    at org.junit.jupiter.api.AssertEquals.assertEquals(AssertEquals.java:177)
    at org.junit.jupiter.api.Assertions.assertEquals(Assertions.java:639)
    at com.xxxxxxx.daos.ScriptDaoTest.testSaveMethod(ScriptDaoTest.java:62)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at java.util.ArrayList.forEach(ArrayList.java:1259)
    at java.util.ArrayList.forEach(ArrayList.java:1259)


Process finished with exit code -1

解决方案

问题根源

ScriptDao继承了DefaultDao,调用save(sql, queryParams)时,实际执行的是自身继承的DefaultDao的save方法,而非测试中mock的DefaultDao实例方法。你mock的DefaultDao对象并未注入到ScriptDao中——因为ScriptDao本身就是DefaultDao的子类,不会依赖外部注入的DefaultDao实例。

修复步骤

  1. 移除冗余的DefaultDao mock:测试中无需@Mock private DefaultDao defaultDao;,只需mock DefaultDao中的依赖JdbcTemplate即可。
  2. 模拟JdbcTemplate的update行为:直接让JdbcTemplate的update方法填充KeyHolder的值,而非mock父类的save方法。
  3. 正确设置KeyHolder的键名:使用数据库自增键的实际列名(比如"id"),确保keyHolder.getKey()能正确获取值。

修改后的测试用例

@ExtendWith(MockitoExtension.class)
@MockitoSettings(strictness = Strictness.LENIENT)
public class ScriptDaoTest {

    @InjectMocks
    private ScriptDao scriptDao;

    @Mock
    private JdbcTemplate jdbcTemplate;

    @Test
    void testSaveMethod() {
        ScriptModel script = new ScriptModel();
        script.setScriptType(1);
        script.setHash("hash123");
        script.setContent("content");

        // 模拟JdbcTemplate的update方法,填充KeyHolder的值
        when(jdbcTemplate.update(any(PreparedStatementCreator.class), any(KeyHolder.class)))
                .thenAnswer(invocation -> {
                    KeyHolder keyHolder = invocation.getArgument(1);
                    // 替换为你的表主键实际列名
                    Map<String, Object> keyMap = new HashMap<>();
                    keyMap.put("id", 1L);
                    keyHolder.getKeyList().add(keyMap);
                    return 1; // 返回受影响行数
                });

        Long result = scriptDao.save(script);

        assertEquals(1L, result);
    }
}

内容的提问来源于stack exchange,提问作者Thiago Apolinario Billieri

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 13:37:04