Skip to content
Johannes Link edited this page Nov 16, 2015 · 1 revision

Why Provide an Assertion API/library?

  • Allows developers to code more explicitly. Somewhat self-documenting.
  • Better reporting
  • Improves readability and structure

Reasons Not to Introduce Yet Another Assertion API/Library

  • Projects like Hamcrest, Fest, AssertJ, and Truth already do this job quite well.
  • Designing a new API for assertions is much harder than many people think.
  • The core JUnit code doesn't need to be made aware of the specific assertion library that the developer uses, so assertion libraries can be released as a separate project.

More discussion

Nice Aspects of the current JUnit Assert API

  • Methods are easy to understand and abundant
  • Promotes safe coding practices when possible (e.g. floating point comparison)
  • Uses a powerful and extensible destructuring and reporting DSL (Hamcrest)

Possibilities for a new API

Reduce to support Predicates only

Java 8 Friday: Most Internal DSLs are Outdated (also referenced on the Materials page)

Pros:

  • Minimal code
  • Fewer bugs
  • No maintenance

Cons:

  • Failed tests are hard to diagnose and debug. (No helpful error messages. Stack traces involving lambdas are most often not helpful.)
  • Intentions are sometimes unclear (what's this Stream of Matchers black magic?)

The cons only apply when one uses only predicates, but instead of predicates we could use function of type T -> Option[Description] for asserting on T. schauder

Bridge Hamcrest

we could provide bridges to Hamcrest and other popular assertion libraries as optional dependencies. After seeing AssertJ, I'm not very happy with JUnit pushing Hamcrest schauder

Need to find a way to bridge between Hamcrest Matchers and FunctionalIntefaces. This includes Functions as well, because all matchers that take an argument can effectively be seen as applying a transform on the input.

Pros:

  • User keeps existing investment in Hamcrest while benefiting from lambdas

Cons:

  • Implementation/syntax could be confusing

Details:

Matcher has two jobs: assert and describe, so has to find a way for a Predicate to do both.

Annotation

Java 8 allows almost any expression to be annotated, so any lambda expression can come with a simple description in an annotation.

Serialized lambda

According to stackoverflow, you can get the name of a serialized function reference (works in Oracle java, but not in Eclipse), so function references can be self-describing.

E.g. one can write assertThat(x, after(X::getSomething, is(y)) and the error message can be something like "Expecting ${x.toString()} after X::getSomething is ${y} but is ${z}".

  • @saff: I spent many weeks trying to get something magical like this to work in a way that I could recommend, and I don't think it's doable. (a) As mentioned above, serialization of lambdas appears to be implementation-dependent. (b) it breaks down when the user wants to talk about operations beyond just a single method call, something that happens a lot of the time in Java 8 code.

Fluent Assertion API

Traditionally, expanding the vocabulary of a fluent system (e.g. AssertJ) requires implementing special classes and using loads of generic tricks. This is not simple nor lambda friendly. I propose we create a new one with minimal built-in asserts but be super extensible through the following methods:

  1. Ad-hoc definition: supply a Predicate/Function with some descriptions: assertThat(person).whose(p => p.getAge(), "age").is(i => i > 30, "greater than 30)
  2. Poor man's traits: extention through combining interfaces:
interface Person {
    String getName();
    int getAge();
}

interface Assertions<T> {
    T actual();

    default void is(T expected) {
        // ...
    }

    // Ad-hoc, other defaults...
}

interface IntegerAssertion extends Assertions<Integer> {
    default void isGreaterThan(int max) { 
        // ...
    }
}

interface PersonAssertion extends Assertions<Person> {
    default IntegerAssertion whoseAge() {
        return actual()::getAge;
    }
}

// Say the build-in library is:
interface Asserts {
    default PersonAssertion assertThat(Person p) {
        return () -> p;
    }
}

// One can trivially extend:
interface MyAsserts extends Asserts {
    interface BetterPersonAssertion extends PersonAssertion {
        default Assertions<String> whoseName() {
            return actual()::getName;
        }
    }

    @Override
    default BetterPersonAssertion assertThat(Person p) {
        return () -> p;
    }
}

// Usage:
class PersonTest implements MyAsserts {
    @Test
    public void checkSoemthing() throws Exception {
        Person p = null;
        assertThat(p).whoseAge().isGreaterThan(30);
    }
}

Pros:

  • Clear and simple
  • Library-neutral: Can easily bridge any existing assertion library (Hamcrest, AssertJ, etc.)
  • Augmentable/Upgradeable: As everything is glued together using interfaces only, one can overlay an entirely different set of code (e.g. to improve error reporting, to bridge another assertion library) on top without affecting user code.

Cons:

  • Yet another library.
  • High levels of overlap with AssertJ/Google Truth

Do we need an assertion API/library in JUnit

Most test frameworks (including non-Java ones) include assertion facilities and searches for "JUnit assert" [eclipse](https://www.google.co.uk/trends/explore#q=junit assert%2C assertj%2Bfest assertion%2Bgoogle truth assertion%2BCheck4J%2C hamcrest) other popular libraries combined, so it's safe to say the users expect one to be there. However, as part of a test framework, we should probable aim for a simple and extensible one. The current Assert class achieves the latter with Hamcrest, but it is not lambda friendly. So I think we should provide another way of extension. - @billc.cn

The question isn't whether JUnit should have an assertion API (it has one already). The question is whether we should build a new assertion API and ship it as part of JUnit Lambda. Again, the core JUnit code would not depend on such an API, so it is a good candidate for functionality that can be created and grow outside of JUnit-core. If you are really passionate about making such an API, how about creating a new project under github/junit-team for this and experiment there? If people love it, we can add it to JUnit-Lambda. - @kcooney

I would prefer more effort to be spent on the "API for Running and Reporting Tests" than on a new assertion API/library. With AssertJ, Hamcrest and Truth there are three assertion libraries under active development. At least AssertJ (never used Truth and have not used Hamcrest for years) is far ahead of anything JUnit could offer. - @PascalSchumacher