assertj icon indicating copy to clipboard operation
assertj copied to clipboard

Provide ability to create custom assumption types in the public API

Open ascopes opened this issue 3 years ago • 2 comments

Feature summary

I am currently writing a library that enables the writing of integration tests that run Java annotation processors and invokes the compiler. Part of this is allowing the user to perform assertions on the compilation result. This is being implemented currently by extending the AbstractAssert class that AssertJ provides.

For example:

var sources = RamPath
    .createPath("sources")
    .createFile(
        "org/example/HelloWorld.java",
        """
        package org.example;
        
        public class HelloWorld {
          public static void main(String[] args) {
            System.out.println("Hello, World!");
          }
        }
        """
    );


var result = Compilers
    .javac()
    .addSourcePath(sources)
    .release(11)
    .compile();
    
assertThatCompilation(result)
    .isSuccessfulWithoutWarnings()
    .classOutput()
    .file("org/example/HelloWorld.class")
    .exists()
    .isNotEmptyFile();

...as part of this, I'd like to be able to provide the ability to assume criteria as well as assert criteria. I noticed that the Assumptions class provides internal detail to create the proxies needed to wrap an AbstractAssert, but unfortunately this API does not appear to be public.

Is there a way to do this for custom assertions, or is it possible to add this into consideration for a future release?

Example

class MyAssertion extends AbstractAssertion<MyAssertion, String> {
  ...

  MyAssertion isCool() {
    if (!"cool".equals(actual)) {
      failWithMessage("That isn't cool!");
    }
    return myself;
  }
}
public static MyAssertion assertThat(String string) {
  return new MyAssertion(string);
}

public static MyAssertion assumeThat(String string) {
  return Assumptions.fromAssertion(new MyAssertion(string));
}

such that Assumptions.fromAssertion can just return a Proxy of some description like the following:

// Not too familiar with ByteBuddy internals, so this is just a pseudocode example.

public class AssumptionProxy implements Proxy {
  private final AbstractAssert assertion;
  
  ...

  @Override
  public Object intercept(Method method, Object[] args) throws Throwable {
    try {
      return method.invoke(assertion, args);
    } catch (InvocationTargetException ex) {
      if (ex.getCause() instanceof AssertionError) {
        // This would cater for junit4, testng, etc like the current internal stuff in
        // assertj would.
        throw new TestAbortedException(ex.getCause());
      } else {
        throw ex;
      }
    }
  }
}

ascopes avatar May 24 '22 08:05 ascopes

That's a fair request, we need to look into details how to do that and figure out the best option here.

joel-costigliola avatar May 24 '22 10:05 joel-costigliola

From https://github.com/assertj/assertj/issues/2713#issuecomment-1199482625, we should consider the following points:

  • Custom assertions do not require a public 1-arg constructor to work as hard assertions.
  • AssertJ has no concrete documentation or guidelines on how third-party libraries should write custom assertions to be compatible with soft assertions.
  • Connecting custom assertion classes with AssertJ core components to get soft assertions support is currently a burden on users and there is no guarantee that it will work (as with XMLUnit).

scordio avatar Aug 02 '22 18:08 scordio