Assertions.assertIterableEquals不识别实际相等对象的问题排查与解决
问题:assertIterableEquals校验失败但对象内容一致
问题场景
我编写的测试中,使用Assertions.assertIterableEquals(expected, result)校验失败,但从错误信息看对象实际内容完全一致:
测试代码:
@Test public void whenArticleWrittenByAuthorOrWithTitleIsExistThenReturnArticleList() { // given List<Article> expected = articleRepository.saveAll(List.of(article)); // when List<Article> result = articleRepository.findByAuthorOrTitle(author, article.getTitle()); // then Assertions.assertIterableEquals(expected, result); }
错误提示:
iterable contents differ at index [0], expected: com.pravvich.model.Article@47d7e8b5 <Article(id=1, author=Author(name=name, login=login), title=title, content=content, tags=[tag])> but was: com.pravvich.model.Article@4353230 <Article(id=1, author=Author(name=name, login=login), title=title, content=content, tags=[tag])>
实体类代码
@Getter @Setter @Entity @ToString @NoArgsConstructor @AllArgsConstructor @Table(name = "articles") public class Article { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Embedded private Author author; @Column(unique = true) private String title; private String content; @ElementCollection(targetClass = String.class, fetch = FetchType.EAGER ) @CollectionTable(name = "tags", joinColumns = @JoinColumn(name = "article_id") ) @Column(name = "tag") private List<String> tags = new ArrayList<>(); @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Article article = (Article) o; if (!Objects.equals(id, article.id)) return false; if (!Objects.equals(author, article.author)) return false; if (!Objects.equals(title, article.title)) return false; if (!Objects.equals(content, article.content)) return false; return Objects.equals(tags, article.tags); } @Override public int hashCode() { int result = id != null ? id.hashCode() : 0; result = 31 * result + (author != null ? author.hashCode() : 0); result = 31 * result + (title != null ? title.hashCode() : 0); result = 31 * result + (content != null ? content.hashCode() : 0); result = 31 * result + (tags != null ? tags.hashCode() : 0); return result; } } @Getter @Setter @ToString @Embeddable @NoArgsConstructor @AllArgsConstructor public class Author { private String name; private String login; @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Author author = (Author) o; if (!Objects.equals(name, author.name)) return false; return Objects.equals(login, author.login); } @Override public int hashCode() { int result = name != null ? name.hashCode() : 0; result = 31 * result + (login != null ? login.hashCode() : 0); return result; } }
更新信息
更新1
问题出在tags集合上。assertIterableEquals仅对List<Article>使用集合对比逻辑,但对List<String> tags仍使用标准equals方法。想了解如何对嵌套集合应用相等判断?
更新2
项目build.gradle配置:
plugins { id 'org.springframework.boot' version '2.7.1' id 'io.spring.dependency-management' version '1.0.11.RELEASE' id 'java' } group = 'com.pravvich' version = '0.0.1-SNAPSHOT' sourceCompatibility = '17' configurations { compileOnly { extendsFrom annotationProcessor } } repositories { mavenCentral() } dependencies { implementation 'org.springframework.boot:spring-boot-starter-actuator' implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-validation' implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.flywaydb:flyway-core' compileOnly 'org.projectlombok:lombok' developmentOnly 'org.springframework.boot:spring-boot-devtools' runtimeOnly 'com.h2database:h2' runtimeOnly 'org.postgresql:postgresql' annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor' annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' } tasks.named('test') { useJUnitPlatform() }
Repository代码:
@Repository public interface ArticleRepository extends JpaRepository<Article, Long>, JpaSpecificationExecutor<Article> { @Query("SELECT a FROM Article a WHERE a.author = :author") List<Article> findAllWrittenBy(Author author); @Query("SELECT t FROM Article a JOIN a.tags t WHERE a.title = :title") List<String> findAllTagsOfArticle(String title); @Query("SELECT a FROM Article a WHERE a.author = :author OR a.title = :title") List<Article> findByAuthorOrTitle(Author author, String title); @Query("SELECT a.author FROM Article a WHERE a.title = :title") Optional<Author> findAuthorOfArticleByTitle(String title); }
原因分析
核心问题是**tags集合的顺序或实现类导致Article.equals()返回false**:
assertIterableEquals的校验逻辑是逐个调用集合元素的equals()方法,而Article的equals()会直接对比tags集合的equals()结果。- List的
equals()要求元素顺序和内容完全一致,若数据库未维护tags的存储顺序,查询返回的tags顺序可能和存入时不一致,导致集合equals()返回false。 - JPA加载
@ElementCollection时可能返回Hibernate的持久化集合实现(如PersistentList),不过这类实现的equals()一般兼容ArrayList,顺序问题才是核心诱因。
修复方案
方案1:维护tags的存储顺序(推荐,适用于需要有序的场景)
在Article的tags字段添加@OrderColumn注解,让数据库维护集合顺序,确保存入和查询的顺序一致:
@ElementCollection(targetClass = String.class, fetch = FetchType.EAGER) @CollectionTable(name = "tags", joinColumns = @JoinColumn(name = "article_id")) @Column(name = "tag") @OrderColumn(name = "tag_order") // 新增注解,指定存储顺序的列 private List<String> tags = new ArrayList<>();
注意:需要更新数据库表,新增tag_order列(可通过Flyway编写迁移脚本实现)。
方案2:修改Article的equals方法,忽略tags顺序(适用于不关心顺序的场景)
调整tags的对比逻辑,只校验元素是否一致,忽略顺序:
@Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Article article = (Article) o; if (!Objects.equals(id, article.id)) return false; if (!Objects.equals(author, article.author)) return false; if (!Objects.equals(title, article.title)) return false; if (!Objects.equals(content, article.content)) return false; // 修改tags对比逻辑 if (tags == null && article.tags == null) return true; if (tags == null || article.tags == null) return false; return tags.size() == article.tags.size() && tags.containsAll(article.tags); }
同时同步修改hashCode,保证与equals逻辑一致:
@Override public int hashCode() { int result = id != null ? id.hashCode() : 0; result = 31 * result + (author != null ? author.hashCode() : 0); result = 31 * result + (title != null ? title.hashCode() : 0); result = 31 * result + (content != null ? content.hashCode() : 0); // 改用HashSet的hashCode,忽略顺序 result = 31 * result + (tags != null ? new HashSet<>(tags).hashCode() : 0); return result; }
方案3:使用AssertJ递归比较(仅修改测试代码,快速解决)
Spring Boot Test自带AssertJ,用递归比较断言可以忽略集合实现类和顺序:
// 替换原有的Assertions.assertIterableEquals assertThat(result) .usingRecursiveComparison() .ignoringCollectionOrder() // 可选:如果需要忽略集合顺序 .isEqualTo(expected);
内容的提问来源于stack exchange,提问作者Pavel
相关产品推荐
相关产品推荐

