DataJpaTest单测全过批量运行失败:列表类型不匹配问题
JpaRepository测试批量运行失败,单独运行通过
问题现象
每个测试单独运行都能通过,但批量运行所有测试时,shouldFindSingleRecipeByName()测试失败,错误信息如下:
expected: "[com.example.recipesapi.model.Recipe@45f421c] (List12@14983265)" but was: "[com.example.recipesapi.model.Recipe@45f421c] (ArrayList@361483eb)" org.opentest4j.AssertionFailedError: expected: "[com.example.recipesapi.model.Recipe@45f421c] (List12@14983265)" but was: "[com.example.recipesapi.model.Recipe@45f421c] (ArrayList@361483eb)"
测试类代码
@DataJpaTest class RecipeRepositoryTest { @Autowired private RecipeRepository recipeRepositoryUnderTest; @BeforeEach void tearDown() { recipeRepositoryUnderTest.deleteAll(); } @Test void shouldFindSingleRecipeByName() { //given String searchName = "Tomato soup"; Recipe recipe1 = new Recipe( 1L, "Tomato soup", "Delicious tomato soup", Arrays.asList("1. ", "2. "), Arrays.asList("1. ", "2. ") ); Recipe recipe2 = new Recipe( 2L, "Mushrooms soup", "Delicious mushrooms soup", Arrays.asList("1. ", "2. "), Arrays.asList("1. ", "2. ") ); List<Recipe> recipes = List.of(recipe1, recipe2); recipeRepositoryUnderTest.saveAll(recipes); //when List<Recipe> recipesList = recipeRepositoryUnderTest.findRecipeByName(searchName.toLowerCase()); //then assertThat(recipesList).isEqualTo(List.of(recipe1)); } @Test void shouldFindTwoRecipesByName() { //given String searchName = "oup"; Recipe recipe1 = new Recipe( 1L, "Tomato soup", "Delicious tomato soup", Arrays.asList("1. ", "2. "), Arrays.asList("1. ", "2. ") ); Recipe recipe2 = new Recipe( 2L, "Mushrooms soup", "Delicious mushrooms soup", Arrays.asList("1. ", "2. "), Arrays.asList("1. ", "2. ") ); List<Recipe> recipes = List.of(recipe1, recipe2); recipeRepositoryUnderTest.saveAll(recipes); //when List<Recipe> recipesList = recipeRepositoryUnderTest.findRecipeByName(searchName.toLowerCase()); //then assertThat(recipesList).isEqualTo(List.of(recipe1, recipe2)); } @Test void findByNameShouldReturnEmptyListOfRecipes() { //given String searchName = "Tomato soup"; //when List<Recipe> recipesList = recipeRepositoryUnderTest.findRecipeByName(searchName.toLowerCase()); //then assertThat(recipesList).isEqualTo(List.of()); } }
Recipe实体类代码
@AllArgsConstructor @NoArgsConstructor @Getter @Entity @Table(name = "Recipes") public class Recipe { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @NotBlank @NotNull @NotEmpty private String name; @NotNull @NotEmpty @NotBlank private String description; @ElementCollection private List<String> ingredients; @ElementCollection private List<String> directions; @Override public boolean equals(final Object o) { if (this == o) return true; if (o == null || Hibernate.getClass(this) != Hibernate.getClass(o)) return false; final Recipe recipe = (Recipe) o; return id != null && Objects.equals(id, recipe.id); } @Override public int hashCode() { return getClass().hashCode(); } }
问题原因及解决方案
1. List实现类不一致导致断言失败
List.of()返回的是JDK内部不可变列表(如List12),而JPA查询返回的是ArrayList。虽然List规范要求equals仅比较内容,但批量运行时测试上下文的差异会导致AssertJ的isEqualTo断言因实例类型不同而失败。
解决方法:
改用AssertJ的内容断言方法,无需关心List的实现类:
// 替换原有的isEqualTo断言 assertThat(recipesList).hasSize(1).containsExactly(recipe1);
或者显式将预期列表转为ArrayList:
assertThat(recipesList).isEqualTo(new ArrayList<>(List.of(recipe1)));
2. 数据库自增序列未重置导致实体ID不匹配
@BeforeEach中的deleteAll()仅删除数据,不会重置数据库自增序列。批量运行时,后续测试的实体ID会从之前的最大值继续递增,而测试代码中手动设置的ID(如1L、2L)与数据库实际生成的ID不一致,导致依赖ID的equals比较失败。
解决方法:
- 不手动设置ID,保存后获取实际持久化的实体进行断言:
// 保存后获取真实的持久化实体 Recipe savedRecipe1 = recipeRepositoryUnderTest.save(recipe1); Recipe savedRecipe2 = recipeRepositoryUnderTest.save(recipe2); // 使用savedRecipe1进行断言 assertThat(recipesList).containsExactly(savedRecipe1);
- 或者在
@BeforeEach中添加重置自增序列的SQL(以H2数据库为例):
@Autowired private JdbcTemplate jdbcTemplate; @BeforeEach void tearDown() { recipeRepositoryUnderTest.deleteAll(); // 重置自增序列 jdbcTemplate.execute("ALTER TABLE Recipes ALTER COLUMN id RESTART WITH 1"); }
内容的提问来源于stack exchange,提问作者user10937817
相关产品推荐
相关产品推荐

