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

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**:

  1. assertIterableEquals的校验逻辑是逐个调用集合元素的equals()方法,而Article的equals()会直接对比tags集合的equals()结果。
  2. List的equals()要求元素顺序和内容完全一致,若数据库未维护tags的存储顺序,查询返回的tags顺序可能和存入时不一致,导致集合equals()返回false。
  3. 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.25 18:54:20