= Lambda Test :toc: :toc-placement: preamble :lambdatestversion: 0.8.0 :documentationversion: {lambdatestversion} :testngversion: 6.11 :junitversion: 4.12 :jupiterversion: 5.9.2 :lambdatest: LambdaTest ifndef::env-asciidoclet[:srcdir: src/main/java/] ifdef::env-asciidoclet[:srcdir:] ifndef::env-asciidoclet[:javasuffix: .java] ifdef::env-asciidoclet[:javasuffix: .html]
ifdef::env-github[] image:https://github.com/lefou/LambdaTest/workflows/.github/workflows/build.yml/badge.svg["Build Status", link="https://github.com/lefou/LambdaTest/actions"] image:https://api.codacy.com/project/badge/Grade/e886bd7ca9784ecfb00fe8afb59b8909["Codacy code quality", link="https://www.codacy.com/app/lefou/LambdaTest"] image:https://javadoc.io/badge2/de.tototec/de.tobiasroeser.lambdatest/javadoc.svg["JavaDoc", link="https://javadoc.io/doc/de.tototec/de.tobiasroeser.lambdatest"] image:https://badges.gitter.im/lefou/LambdaTest.svg["Chat on Gitter", link="https://gitter.im/lefou/LambdaTest"] endif::[]
ifndef::env-github[Project Homepage: https://github.com/lefou/LambdaTest]
Lambda-enabled functional testing on top of JUnit and TestNG.
Use the same DSL with any testing framework.
Documentation for LambdaTest {documentationversion}.
== Motivation
When constrained to work in a Java-only toolchain, I really miss http://scalatest.org[ScalaTest]. I tried some lambda enabled test frameworks, but until now (2014), did not find a suitable solution without compromizing the integration benefits.
Thus, I decided to write a small and generic test library that allows writing of functional test without reinventing the wheel. LambdaTest works on top of JUnit and TestNG, all you need is to add it to the test classpath. No further adaptions to your existing test setup are needed. You will immediately gain the joy of Lambda-enabled functional testing, better assertion messages and nicely colored output.
== Features
Most important features are:
intercept
expectXXX
-methodsexpectXXX
-methods (see more than the first assertion error)== Documentation
Beside this document, you can also read the https://javadoc.io/doc/de.tototec/de.tototec.utils.functional[JavadDoc for LambdaTest]
== Download from Maven Central
{lambdatest} is available from http://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22de.tototec%22%20AND%20a%3A%22de.tobiasroeser.lambdatest%22[Maven central repository].
Maven users can use the following dependency declaration:
== Choose your favorite Unit-Test Runner: TestNG, JUnit, Junit5 (Jupiter)
With {lambdatest}, you need to only know LambdaTests very simple and minimalistic API but can use it to write test for JUnit and TestNG.
To avoid a dependency to both frameworks at the same time, your test class inherits a different base class, but besides that, everything else is the same.
For JUnit you inherit link:{srcdir}de/tobiasroeser/lambdatest/junit/FreeSpec{javasuffix}[de.tobiasroeser.lambdatest.junit.FreeSpec
].
For Junit 5 (Jupiter) you inherit link:{srcdir}de/tobiasroeser/lambdatest/junit5/FreeSpec{javasuffix}[de.tobiasroeser.lambdatest.junit5.FreeSpec
].
For TestNG you inherit
link:{srcdir}de/tobiasroeser/lambdatest/testng/FreeSpec{javasuffix}[de.tobiasroeser.lambdatest.testng.FreeSpec
].
If you use LambdaTest with mill.testng.TestNGFramework
(in Mill or sbt), you may see extra verbose output.
You can disable the progress output of mill.testng.TestNGFramework
by setting the mill.testng.printProgress
property to 0
.
build.sc
: Make mill.testng.TestNGFramework
runner less verbose in Mill
[source,scala,subs="attributes"]--
== Writing tests with Lambda Test
The test cases can be defined in various places.
protected void initTests()
methodHere you see a basic test example, which produces a valid TestNG test class.
You need to extend from class de.tobiasroeser.lambdatest.testng.FreeSpec
.
import static de.tobiasroeser.lambdatest.Expect.expectEquals; // You can also use JUnit or Junit 5 (Jupiter) based tests with // import de.tobiasroeser.lambdatest.junit.FreeSpec; // import de.tobiasroeser.lambdatest.junit5.FreeSpec; import de.tobiasroeser.lambdatest.testng.FreeSpec;
public class SimpleTest extends FreeSpec { public SimpleTest() {
test("1 + 1 = 2", () -> {
expectEquals(1 + 1, 2);
});
test("a pending test", () -> pending());
test("divide by zero", () -> {
int a = 2;
int b = 0;
intercept(ArithmeticException.class, () -> {
int c = a / b;
});
});
section("A String should", () -> {
final String aString = "A string";
test("match certain criteria", () -> {
expectString(aString)
.contains("string")
.containsIgnoreCase("String")
.startsWith("A")
.endsWith("ng")
.hasLength(8);
});
test("be not longer than 2", () -> {
expectString(aString).isLongerThan(2);
});
});
test("demo of a fail", () -> {
"yes".equals("yes and no");
});
{
test("test in initializer", () -> {
expectTrue(true);
});
}
}
The methods test
, pending
and intercept
are provided by FreeSpec
whereas the usual expectXXX
methods are provided by Expect
.
The output of this test suite above would look like this:
image:Screenshot_SimpleTest.jpg[]
You can run the above test directly in the {lambdatest} project directory with:
--
You should write your test cases so that they don't need to be executed in order.
{lambdatest} is able to run tests in parallel, if you enable it explicitly with FreeSpec.setRunInParallel(true)
.
By default expectXXX
-methods fail fast, which means the first failing assertion will end the whole test.
This is also the behaviour you will get with most other test frameworks.
But you can disable fail-fast behaviour for assertions/expectations with FreeSpec.setExpectFailFast(false)
.
Then, the first failing expectXXX
-error will not abort the test but the test is optimistically continued.
Further failing assertion errors are collected and the test fails at the end, reporting all collected errors.
== Writing assertions with Expect
{lambdatest} provides many methods in the class de.tobiasroeser.lambdatest.Expect
to write assertion.
You can use these as an alternative to the assertion methods provides by other unit testing framework to gain the following advantages:
expectXXX
-methods provide a feature to collect multiple assertions (non-fail-fast behaviour), such that you can collect as much errors as possible in one test run, instead of giving up at the first error..Selected static methods of Expect
expectNull
- Assert that a given value is nullexpectNotNull
- Assert that a given value is not nullexpectEquals
- Assert equality of two given objects or values.expectNotEquals
- Assert non-equality of two given objects or values.expectTrue
- Assert a value evaluates to true
expectFalse
- Assert a value evaluates to false
expectDouble
- Assert that a given double is non-null and return an instance of ExpectDouble
with provides further checks on the actual double in a fluent APIexpectString
- Assert that a given string is non-null and return an instance of ExpectString
with provides further checks on the actual string in a fluent APIexpectCollection
- Assert that a given collection is non-null and return an instance of ExpectCollection
with provides further checks on the actual colletion in a fluent APIexpectMap
- Assert that a given map is non-null and return an instance of ExpectMap
with provides further checks on the actual map in a fluent APIintercept
- Assert that a code block throws an Exception of the given type and optional with an message matching a given regular expression. Returns the thrown exception for further analysisThere are more method in Expect
with setup and control it non-fail-fast handling via ThreadLocals. Those are only needed, if you want to use these behaviour outside of FreeSpec
.
Expect
class outside of FreeSpec
, you have to take care of setup and finalization by yourself.== Fluent API to investigate common types
All fluet API ExpectXXX
classes support the fail-late behaviour.
=== Analyze Strings with ExpectString
To inspect and assert strings, use the class ExpectString
, which is also returned, if you use Expect.expectString
.
.Methods of ExpectString
isEqual
isNotEqual
isEqualIgnoreCase
isNotEqualIgnoreCase
startsWith
StartsWithNot
endsWith
endsNotWith
matches
matchesNot
hasLength
hasLengthNot
isLongerThan
isShorterThan
isTrimmed
contains
containsNot
containsIgnoreCase
containsIgnoreCaseNot
=== Analyze Collections with ExpectCollection
To inspect and assert collections, use the class ExpectCollection
, which is also returned, if you use Expect.expectCollection
.
=== Analyze Maps with ExpectMap
To inspect and assert maps, use the class ExpectMap
, which is also returned, if you use Expect.expectMap
.
=== Analyze Doubles with ExpectDouble
To inspect and assert doubles, use the class ExpectMap
, which is also returned, if you use Expect.expectDouble
.
.Methods of ExpectDouble
isCloseTo
isNotCloseTo
isBetween
isNotBetween
isNaN
isNotNaN
== Testing files and directories with TempFile
{lambdatest} comes with a helper class de.tobiasroeser.lambdatest.TempFile
which contains useful methods to work with temporary files.
To create a temporary file with a given content and do something with it, you can use TempFile.withTempFile
or it procedural version with does not return a value TempFile.withTempFileP
. After the method completes, the temporary file will automatically deleted.
To create and work with temporary files, you can use TempFile.withTempDir
and TempFile.withTempDirP`. Those will be recursively deleted after completion.
There are more useful methods in class TempFile
, e.g. readFile
, writeToFile
and deleteRecursive
. Please inspect the class for more information.
== Using TestProxy
to create mock objects
The general idea in unit testing is to isolate a class under test from its dependencies.
An naive way to do this is to create dummy implementations, but this can be a very cumbersome, repetitive and booring task. Also it creates a lot of boilerplate code, which is also unnecessary hard to maintain.
On the opposite end, you can find very advanced mocking frameworks which will create mocks that can be trained and replayed, but the resulting code is no longer easy to understand and also adds lots of new dependencies.
Therefore in the middle there is TestProxy
to easily create dummy proxy instances.
By default, each invoked method on the proxy will throw an UnsupportedOperationException
with a meaningful detail message.
You can also provide explicit behaviour to your proxy by providing delegate objects. Whenever a method is invoked on the proxy, the given objects will be checked if they contain a method with a matching signature, and if so, that method will be invoked an behalf of the proxy.
You can either use the more explicit way with TestProxy.proxy(ClassLoader, List<Class<?>>, List<Object>)
or the more convenient and compact TestProxy.proxy(Object...)
method.
TestProxy
[source,java]package org.example;
import static de.tobiasroeser.lambdatest.Expect.expectEquals;
import de.tobiasroeser.lambdatest.proxy.TestProxy; import de.tobiasroeser.lambdatest.testng.FreeSpec;
public class ExampleProxyTest extends FreeSpec { interface Dependency { String hello(); }
class ServiceWithDependency { private Dependency dependency;
public ServiceWithDependency(final Dependency dependency) {
this.dependency = dependency;
}
String usingDependency() {
return dependency.hello();
}
String notUsingDependency() {
return "Have a nice day!";
}
}
public ExampleProxyTest() {
test("A proxy without delegates as optional dependencies should be sufficient", () -> {
final Dependency dep = TestProxy.proxy(Dependency.class);
final ServiceWithDependency service = new ServiceWithDependency(dep);
expectEquals(service.notUsingDependency(), "Have a nice day!");
});
test("A proxy without delegates as mandatory dependencies should fail", () -> {
final Dependency dep = TestProxy.proxy(Dependency.class);
final ServiceWithDependency service = new ServiceWithDependency(dep);
intercept(UnsupportedOperationException.class, () -> {
service.usingDependency();
});
});
test("A proxy with delegates as mandatory dependency should succeed", () -> {
final Dependency dep = TestProxy.proxy(Dependency.class, new Object() {
@SuppressWarnings("unused")
public String hello() {
return "Hello Proxy!";
}
});
final ServiceWithDependency service = new ServiceWithDependency(dep);
expectEquals(service.usingDependency(), "Hello Proxy!");
});
== Working on Java7
Even though writing functional test makes most sense under Java 8+, there are enough reasons to also use them on older Java versions which do not provide nice closures.
{lambdatest} versions up to 0.7.1 didn't use any Java 8 API! You can download pre-compiled binaries of {lambdatest} for older Java 7 Runtimes. To use the non-Java8 version with Maven, use a classifier ("java7") to download the version you want. The compatibility packages were produced with the great https://github.com/orfjackal/retrolambda[retrolambda project].
To use the latest Java7 compatible version 0.7.1 in Maven:
Instead of Java 8 Closures, you have to create anonymous classes.
import static de.tobiasroeser.lambdatest.Expect.expectEquals; import de.tobiasroeser.lambdatest.RunnableWithException; import de.tobiasroeser.lambdatest.junit.FreeSpec;
class SimpleTest extends FreeSpec { public SimpleTest() {
test("1 + 1 = 2", new RunnableWithException() {
public void run() throws Exception {
expectEquals(1 + 1, 2);
}
});
test("divide by zero", new RunnableWithException() {
public void run() throws Exception {
int a = 2;
int b = 0;
intercept(ArithmeticException.class, new RunnableWithException() {
public void run() throws Exception {
int c = a / b;
}
});
}
});
== Build {lambdatest} from Source
=== Building with Maven
{lambdatest} is build with https://maven.apache.org[Apache Maven 3.3.1] and the https://github.com/takari/polyglot-maven#overview[polyglot-scala extension]. Maven 3.5 is recommended.
The built JARs file can be found in the target
directory.
=== Create pom.xml
for interoperability, e.g. IDEs
To generate the pom.xml
use the gen-pom-xml
profile.
pom.xml
filespom.xml
files== Licence
This project is published under the http://www.apache.org/licenses/LICENSE-2.0.txt[Apache Licence Version 2.0].
== Contribution / Contact
Your feedback is highly appreciated. I also accept pull request.
For questions please use the https://gitter.im/lefou/LambdaTest[Gitter chatroom]. To report issues or send pull request, use https://github.com/lefou/LambdaTest[GitHub].
You can also find me on https://twitter.com/TobiasRoeser[Twitter as @TobiasRoeser].
If you like {lambdatest}, please star it on https://github.com/lefou/LambdaTest[GitHub]. This will help me to set my priorities. Thanks!
== Changelog
=== LambdaTest 0.8.0 - 2023-02-28
=== LambdaTest 0.7.1 - 2021-06-28
initTests()
)=== LambdaTest 0.7.0 - 2019-01-10
Expect.expectDouble
and ExpectDouble
class to assert properties of double values with a fluent API.=== LambdaTest 0.6.2 - 2018-08-01
Import-Package
present in older releases.=== LambdaTest 0.6.1 - 2018-07-24
java.lang.AssertionError
was used, which was only introduced in Java 7.)TestProxy
gained better copy'n'paste able error message in case of unimplemented methods were call. === LambdaTest 0.6.0 - 2018-06-22
Expect.expectCollection
and ExpectCollection
class to assert properties of collection with a fluent API.TestProxy
now properly passes exceptions thrown by delegate objects.FreeSpecBase.setDefaultReporter
.Expect.expectMap
and ExpectMap
class to assert properties of maps with a fluent API.=== LambdaTest 0.5.0 - 2018-06-11
TestProxy
in package de.tobiasroeser.lambdatest.proxy
to easily create mock dependencies / proxies.=== LambdaTest 0.4.0 - 2018-03-05
internal.Logger
and internal.LoggerFactory
to wrap either an
Slf4j-Logger or a JUL-Logger, both supporting Slf4j parameter placeholders.LoggingWrappingReporter
which logs to an logging frameorg and
delegates all methods to an underlying Reporter.Assert.assertXXX
and
Expect.expectXXX
methods.
If given, the msg-parameter does no longer replace the generic assertion
message, instead both messages are shown, first the given message, then the
generic message.ClassCastExcpetion
for
primitive type arrays.=== LambdaTest 0.3.1 - 2018-02-13
DefaultReporter
can now hide the stacktraceexpectEquals
message for number and arguments of different typesexpectNotNull
behaviour=== LambdaTest 0.3.0 - 2017-10-30
Reporter
interface and DefaultReporter
classFreeSpecBase
to hold test framework unspecific logicFreeSpec.section
to allow more structured testsReporter
)Assert.assertNull
and Assert.assertNotNull
Expect.expectNull
and Expect.expectNotNull
Intercept.intercept(Class<T>, String, RunnableWithException)
=== LambdaTest 0.2.4 - 2016-08-03
=== LambdaTest 0.2.3 - 2016-05-10
=== LambdaTest 0.2.2 - 2016-05-03
=== LambdaTest 0.2.1 - 2016-03-04
TempFile
, an utility class providing support to work with temporary files and directories which will be automatically cleaned up after the test case.=== LambdaTest 0.2.0 - 2016-02-12
de.tobiasroeser.lambdatest.junit.FreeSpec
for JUnit based tests in addition to the already existing de.tobiasroeser.lambdatest.testng.FreeSpec
for TestNG based tests.Assert
and Expect
classes. Expect-based asserts also support deferred exceptions. FreeSpec already integrates the setup of Expect.=== LambdaTest 0.1.0 - 2014-12-08
=== Rerelease of LambdaTest 0.0.3 - 2014-11-29
=== LambdaTest 0.0.3 - 2014-11-16
=== LambdaTest 0.0.2 - 2014-10-18
=== LambdaTest 0.0.1 - 2014-10-15