AssertJ Integration
Runnable example:
AssertJSampleTest.java
The fun-assertj module provides fluent AssertJ custom assertions for all
dmx-fun types. It is an optional test-only dependency — the core fun
library has no runtime dependency on AssertJ.
Adding the dependency
fun-assertj declares assertj-core as compileOnly. You must add
assertj-core explicitly to your own test classpath. Any version from
3.21.x through 3.27.x is supported (see version compatibility below).
// Gradle — test scope onlytestImplementation("codes.domix:fun-assertj:0.1.0")// AssertJ itself — bring your own version (3.21.x – 3.27.x)testImplementation("org.assertj:assertj-core:3.27.7")<!-- Maven — test scope only --><dependency> <groupId>codes.domix</groupId> <artifactId>fun-assertj</artifactId> <version>0.1.0</version> <scope>test</scope></dependency><dependency> <groupId>org.assertj</groupId> <artifactId>assertj-core</artifactId> <version>3.27.7</version> <scope>test</scope></dependency>Entry point
All assertions are accessed through a single overloaded static method:
import static dmx.fun.assertj.DmxFunAssertions.assertThat;DmxFunAssertions.assertThat coexists with
org.assertj.core.api.Assertions.assertThat — both can be statically imported
in the same test class without naming conflict, because the argument types differ.
Note:
fun-assertjcannot be used inside the:libmodule’s own tests due to a circular dependency. Use it only in consumer projects or in the:assertjmodule’s own test suite.
Assertions reference
| Type | Methods |
|---|---|
Either<L, R> | isRight(), isLeft(), containsRight(expected), containsLeft(expected) |
Option<T> | isSome(), isNone(), containsValue(expected), hasValueSatisfying(consumer) |
Result<V, E> | isOk(), isErr(), containsValue(expected), containsError(expected) |
Try<V> | isSuccess(), isFailure(), containsValue(expected), failsWith(exceptionType) |
Validated<E, A> | isValid(), isInvalid(), containsValue(expected), hasError(expected) |
Tuple2<A, B> | hasFirst(expected), hasSecond(expected) |
Tuple3<A, B, C> | hasFirst(expected), hasSecond(expected), hasThird(expected) |
Tuple4<A, B, C, D> | hasFirst(expected), hasSecond(expected), hasThird(expected), hasFourth(expected) |
Resource<T> | succeedsWith(expected), failsWith(exceptionType), failsWithMessage(message) |
Guard<T> | accepts(value), rejects(value), rejectsWithMessage(value, message), rejectsWithMessages(value, messages...) |
Accumulator<E, A> | hasValue(expected), hasAccumulation(expected), accumulationContains(element), accumulationHasSize(size) |
Either<L, R> assertions
import static dmx.fun.assertj.DmxFunAssertions.assertThat;
// Assert Right and its value — chainable in one expressionassertThat(Either.<String, Integer>right(42)) .isRight() .containsRight(42);
// Assert Left and its value — chainable in one expressionassertThat(Either.<String, Integer>left("oops")) .isLeft() .containsLeft("oops");
// isRight() / isLeft() alone when the value does not matterassertThat(Either.right("ok")).isRight();assertThat(Either.left("err")).isLeft();Option<T> assertions
import static dmx.fun.assertj.DmxFunAssertions.assertThat;
// Assert presence and valueassertThat(Option.some(42)) .isSome() .containsValue(42);
// Assert absenceassertThat(Option.<String>none()) .isNone();
// Assert the value satisfies a condition without extracting itassertThat(Option.some("hello")) .isSome() .hasValueSatisfying(v -> assertThat(v).startsWith("hel").hasSize(5));Result<V, E> assertions
import static dmx.fun.assertj.DmxFunAssertions.assertThat;
// Assert Ok and its valueassertThat(Result.ok("hello")) .isOk() .containsValue("hello");
// Assert Err and its errorassertThat(Result.<String, String>err("oops")) .isErr() .containsError("oops");
// Combining with standard AssertJ — both static imports coexistassertThat(Result.ok(List.of(1, 2, 3))) .isOk() .containsValue(List.of(1, 2, 3));Try<V> assertions
import static dmx.fun.assertj.DmxFunAssertions.assertThat;
// Assert success and valueassertThat(Try.success("data")) .isSuccess() .containsValue("data");
// Assert failure — just check it failedassertThat(Try.failure(new RuntimeException("boom"))) .isFailure();
// Assert failure with a specific exception typeassertThat(Try.of(() -> { throw new IOException("disk full"); })) .isFailure() .failsWith(IOException.class);
// Assert a computation succeedsassertThat(Try.of(() -> Integer.parseInt("42"))) .isSuccess() .containsValue(42);Validated<E, A> assertions
import static dmx.fun.assertj.DmxFunAssertions.assertThat;
// Assert valid and valueassertThat(Validated.valid("ok")) .isValid() .containsValue("ok");
// Assert invalid and errorassertThat(Validated.<String, String>invalid("bad input")) .isInvalid() .hasError("bad input");
// Assert NEL-based validation resultValidated<NonEmptyList<String>, Integer> result = Validated.invalidNel("must be positive");
assertThat(result) .isInvalid() .hasError(NonEmptyList.singleton("must be positive"));Tuple2/3/4 assertions
import static dmx.fun.assertj.DmxFunAssertions.assertThat;
// Tuple2assertThat(Tuple2.of("Alice", 30)) .hasFirst("Alice") .hasSecond(30);
// Tuple3assertThat(Tuple3.of("Alice", 30, true)) .hasFirst("Alice") .hasSecond(30) .hasThird(true);
// Tuple4assertThat(Tuple4.of("Alice", 30, true, 1.75)) .hasFirst("Alice") .hasSecond(30) .hasThird(true) .hasFourth(1.75);Resource<T> assertions
The first assertion called on a ResourceAssert runs the full acquire-use-release
cycle once (via use(v -> v)) and caches the result. All subsequent chained
assertions on the same instance reuse that cached Try — the lifecycle is never
triggered more than once. Use succeedsWith to verify that acquisition and
release complete without error; use failsWith / failsWithMessage to verify
that the cycle fails in the expected way.
import static dmx.fun.assertj.DmxFunAssertions.assertThat;
// Successful acquire + use + releaseResource<String> file = Resource.of( () -> "file-content", // acquire content -> { }); // release
assertThat(file).succeedsWith("file-content");
// Acquisition failure — verify exception typeResource<String> broken = Resource.of( () -> { throw new IOException("file not found"); }, content -> { });
assertThat(broken).failsWith(IOException.class);
// Acquisition failure — verify exception messageassertThat(broken).failsWithMessage("file not found");
// Chain both failure checksassertThat(broken) .failsWith(IOException.class) .failsWithMessage("not found");
// AutoCloseable shorthandResource<BufferedReader> reader = Resource.fromAutoCloseable( () -> new BufferedReader(new FileReader("data.txt")));
assertThat(reader).failsWith(FileNotFoundException.class);Guard<T> assertions
Guard.check(value) returns Validated<NonEmptyList<String>, T>. The assertion
methods call check internally — you only supply the value to validate and the
messages you expect to find in the rejection error list.
import static dmx.fun.assertj.DmxFunAssertions.assertThat;
Guard<String> alphanumeric = Guard.of( s -> s.matches("[a-zA-Z0-9]+"), "must be alphanumeric");
// Assert acceptanceassertThat(alphanumeric).accepts("alice123");
// Assert rejection (any invalid value)assertThat(alphanumeric).rejects("!!");
// Assert a specific rejection message is presentassertThat(alphanumeric).rejectsWithMessage("!!", "alphanumeric");
// Combined guard: assert all expected messages appearGuard<String> minLength = Guard.of(s -> s.length() >= 3, "must be at least 3 chars");Guard<String> username = alphanumeric.and(minLength);
assertThat(username).rejectsWithMessages("!!", "alphanumeric", "at least 3 chars");
// Fluent chainassertThat(username) .accepts("alice") .rejects("!!") .rejectsWithMessage("ab", "at least 3 chars");Accumulator<E, A> assertions
accumulationContains and accumulationHasSize require the accumulated
side-channel (E) to be a non-null java.util.Collection. If it is null or
not a Collection, the assertion fails with a descriptive error. Use
hasAccumulation for equality checks on any accumulation type, including
non-collection and nullable values.
import static dmx.fun.assertj.DmxFunAssertions.assertThat;
BinaryOperator<List<String>> concat = (a, b) -> { var merged = new ArrayList<>(a); merged.addAll(b); return merged;};
Accumulator<List<String>, Integer> result = Accumulator.of(10, List.of("loaded base value")) .flatMap(v -> Accumulator.of(v * 2, List.of("doubled")), concat) .flatMap(v -> Accumulator.of(v + 5, List.of("added 5")), concat);
// Assert the primary computed valueassertThat(result).hasValue(25);
// Assert individual log entries are presentassertThat(result) .accumulationContains("loaded base value") .accumulationContains("doubled") .accumulationContains("added 5");
// Assert total number of log entriesassertThat(result).accumulationHasSize(3);
// Assert the exact accumulationassertThat(result) .hasAccumulation(List.of("loaded base value", "doubled", "added 5"));
// Fluent chainassertThat(result) .hasValue(25) .accumulationContains("loaded base value") .accumulationHasSize(3);Version compatibility
The module is tested in CI against the following AssertJ versions on every pull
request that touches the assertj/ module:
| AssertJ version | Status |
|---|---|
| 3.21.x | tested |
| 3.22.x | tested |
| 3.23.x | tested |
| 3.24.x | tested |
| 3.25.x | tested |
| 3.26.x | tested |
| 3.27.x | tested |
To run the tests locally against a specific version:
./gradlew :assertj:test -PassertjVersion=3.25.3