AssertJ:为 Set<A extends B> 生成流畅的断言

AssertJ: generating fluent assertions for Set<A extends B>

我偶然发现了一个问题,AssertJ 在其中一个断言中生成了以下代码 类:

public S hasItems(interface ItemInterface... items)

这当然不能编译。

导致问题的示例代码如下:


    public interface EntityInterface {

      Set<? extends ItemInterface> getItems();
    }

    @NoArgsConstructor
    @AllArgsConstructor
    @Data
    @With
    public class EntityA implements EntityInterface {

      private Set<ItemA> items;
    }

    @NoArgsConstructor
    @AllArgsConstructor
    @Data
    @With
    public class EntityA implements EntityInterface {

      private Set<ItemA> items;
    }

    public interface ItemInterface {

      String getName();
    }

    public class ItemA implements ItemInterface {

      public String getName() {
        return "ItemA";
      }
    }

    public class ItemA implements ItemInterface {

      public String getName() {
        return "ItemA";
      }
    }

我已经包含了导致此错误的最小示例项目,因此可以亲眼看到。可以从filebin

下载

我们正在使用 Lombok 的 @With 注释以及其他考虑因素,需要保留接口。

为了解决这个问题,我试过:

  1. 将 getItems 方法签名更改为:

<T extends ItemInterface> Set<T> getItems();

产生:

public S hasItems(T... items)

然而 T 在上下文中是未知的。

  1. 使用以下方法将界面变成模板:

public interface EntityInterface<T extends ItemInterface>

这没有任何区别。

是否有我遗漏的解决方案?

如您所见,问题在于 AssertJ 使用无效方法创建 AbstractEntityInterfaceAssert class,例如:

public S hasItems(interface ItemInterface... items) {/*...*/}

我没有使用 AssertJ 的实际经验,但经过一些研究后,我找到了 2 个保留编译时类型安全的解决方法(将 EntityInterface.getItems() 方法更改为 return Set<?> 有效,但不可接受):

  1. 使用实现接口的抽象class,而不是直接使用接口:
public interface ItemInterface {
  String getName();
}

public abstract class AbstractItem implements ItemInterface {
}

public class ItemA extends AbstractItem {
  public String getName() {
    return "ItemA";
  }
}

// ItemB same as ItemA

public interface EntityInterface {
  Set<? extends AbstractItem> getItems();
}

@NoArgsConstructor
@AllArgsConstructor
@Data
@With
public class EntityA implements EntityInterface {
  private Set<ItemA> items;
}

// EntityB same as EntityA, but with Set of ItemB

可以看出,与您的示例相比,唯一的变化是 AbstractItem 用作基础 class 而不是在 ItemA 和 [ 中实现 ItemInterface =19=] 和 EntityInterface.getItems() 方法更改为 return Set<? extends AbstractItem> 而不是 Set<? extends ItemInterface>

通过这些更改,程序可以正确编译并生成 AbstractEntityInterfaceAssert class 具有有效的方法签名,例如:

public S hasItems(AbstractItem... items) { /*...*/ }
  1. 第二种解决方法是使用 assertj-assertions-generator-maven-plugin settings:
  2. 从生成中排除 EntityInterface
    <build>
        <plugins>
            <plugin>
                <groupId>org.assertj</groupId>
                <artifactId>assertj-assertions-generator-maven-plugin</artifactId>
                <!-- ... -->
                <configuration>
                    <!-- ... -->
                    <!-- Exclude classes matching the regex from generation -->
                    <excludes>
                        <param>com.example.EntityInterface</param>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

以上配置防止生成AbstractEntityInterfaceAssert.java

我不知道这些解决方法是否适用于您的用例,不幸的是我无法提供更好的解决方案或解释(这是 AspectJ 的错误或限制吗?)。最好的人选是 Joel Costigliola - AssertJ

的作者

有用的读物​​:

我曾在 https://github.com/joel-costigliola/assertj-assertions-generator 中尝试支持泛型,结果发现问题相当复杂,不幸的是,由于缺乏好的解决方案和其他优先事项,我不得不放弃 :(。

我的最佳答案是,生成器只是一种快速获取自定义断言的方法,它并不意味着完美,因此一旦生成,请根据您的需要进行更改 - 阅读 http://joel-costigliola.github.io/assertj/assertj-assertions-generator.html#philosophy

希望我的回答不会太令人失望。