Skip to content

Instantly share code, notes, and snippets.

@blast-hardcheese
Last active September 2, 2021 07:17
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save blast-hardcheese/0cda9d27f46a2d07b62d47d293a72366 to your computer and use it in GitHub Desktop.
Save blast-hardcheese/0cda9d27f46a2d07b62d47d293a72366 to your computer and use it in GitHub Desktop.
Combinator-style assertion "library", in Scala and Java
import java.util.function.Function;
import java.util.Arrays;
class CheckThat<A> {
// Wrap a simple assertion
Function<A, Boolean> func;
public CheckThat(Function<A, Boolean> func) {
this.func = func;
}
// ... which returns true if the predicate passes
public Boolean check(A value) {
return this.func.apply(value);
}
// ... and provides a mechanism for widening the type in some way
public <B> CheckThat<B> contramap(Function<B, A> func) {
return new CheckThat<B>(func.andThen(this.func));
}
// ... as well as a convenience function to combine multiple tests of the same type
@SafeVarargs
public static <A> CheckThat<A> all(CheckThat<A> ...checkers) {
return new CheckThat<A>(value ->
Arrays.stream(checkers).allMatch(checker -> checker.check(value))
);
}
// ... and a convenience function for a straight property assertion
public static <A> CheckThat<A> one(Function<A, Boolean> func) {
return new CheckThat<A>(func);
}
public static <A> CheckThat<A> one(A value) {
return new CheckThat<A>(a -> a == value);
}
public static <A, B> CheckThat<A> hasField(Function<A, B> proj, CheckThat<B> check) {
return check.contramap(proj);
}
}
// mock data
class Foo {
public Long getX() { return 5L; }
public String getY() { return "boop"; }
}
class Bar {
public Foo getFoo() {
return new Foo();
}
}
public class App {
public static void main(String[] args) {
// Boilerplate, if you happen to have a bunch of repeated assertions that all need the same value
CheckThat<String> stringEq = CheckThat.one("boop");
CheckThat<Long> longEq = CheckThat.one(5L);
// When writing assertions for Foo, I...
CheckThat<Foo> checkThatFoo =
CheckThat.all( // ... accumulate all checks entailing...
CheckThat.one(
f -> f.getX() == 5L // a direct check, logic all bundled together
),
CheckThat.hasField(
f -> f.getY(), // a check for Y
stringEq // ... which satisfies some property
),
longEq.contramap( // a check for some property
f -> f.getX() // ... on the result of some method
),
CheckThat.hasField( // an explicit check for...
f -> f.getX(), // ... a field which
CheckThat.one(
l -> l == 5L // ... satisfies this property.
)
)
);
// ... all within a Bar
CheckThat<Bar> checkThatBar =
checkThatFoo.contramap(b -> b.getFoo());
// mock data
Bar bar = new Bar();
// and this passes:
System.out.println(checkThatBar.check(bar).toString());
}
}
// Wrap a simple assertion
class CheckThat[A](func: A => Boolean) { self =>
// ... which returns true if the predicate passes
def check(value: A): Boolean = func(value)
// ... and provides a mechanism for widening the type in some way
def contramap[B](from: B => A): CheckThat[B] = new CheckThat[B](value => self.check(from(value)))
}
object CheckThat {
// ... as well as a convenience function to combine multiple tests of the same type
def all[A](xs: CheckThat[A]*): CheckThat[A] = new CheckThat[A](value => xs.forall(x => x.check(value))) // Create a new supertest that combines the subtests, but returns the same type
// ... and a convenience function for a straight property assertion
def one[A](pred: A => Boolean): CheckThat[A] = new CheckThat[A](pred)
def hasField[A, B](func: A => B, check: CheckThat[B]): CheckThat[A] = check.contramap(func)
}
// Boilerplate, if you happen to have a bunch of repeated assertions that all need the same value
val stringEq = CheckThat.one[String](_ == "boop")
val longEq = CheckThat.one[Long](_ == 5L)
// mock data
class Foo() { def getX(): Long = 5L; def getY(): String = "boop" }
class Bar() { def getFoo(): Foo = new Foo() }
// When writing assertions for Foo, I...
val checkThatFoo: CheckThat[Foo] =
CheckThat.all[Foo]( // ... accumulate all checks entailing...
CheckThat.one(
_.getX() == 5L // a direct check, logic all bundled together
),
CheckThat.hasField(
_.getY(), // a check for Y
stringEq // ... which satisfies some property
),
longEq.contramap( // a check for some property
_.getX() // ... on the result of some method
),
CheckThat.hasField( // an explicit check for...
_.getX(), // ... a field which
CheckThat.one[Long](
_ == 5L // ... satisfies this property.
)
)
)
// ... all within a Bar
val checkThatBar: CheckThat[Bar] =
checkThatFoo.contramap[Bar](b => b.getFoo())
// and this passes:
val bar = new Bar()
checkThatBar.check(bar) // returns true
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment