Last active
September 2, 2021 07:17
-
-
Save blast-hardcheese/0cda9d27f46a2d07b62d47d293a72366 to your computer and use it in GitHub Desktop.
Combinator-style assertion "library", in Scala and Java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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()); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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