Lambda-enabled functional testing on top of JUnit and TestNG.
Use the same DSL with any testing framework.
Documentation for LambdaTest 0.8.0.
- Motivation
- Features
- Documentation
- Download from Maven Central
- Choose your favorite Unit-Test Runner: TestNG, JUnit, Junit5 (Jupiter)
- Writing tests with Lambda Test
- Writing assertions with
Expect
- Fluent API to investigate common types
- Testing files and directories with
TempFile
- Using
TestProxy
to create mock objects - Working on Java7
- Build LambdaTest from Source
- Licence
- Contribution / Contact
- Changelog
- LambdaTest 0.8.0 - 2023-02-28
- LambdaTest 0.7.1 - 2021-06-28
- LambdaTest 0.7.0 - 2019-01-10
- LambdaTest 0.6.2 - 2018-08-01
- LambdaTest 0.6.1 - 2018-07-24
- LambdaTest 0.6.0 - 2018-06-22
- LambdaTest 0.5.0 - 2018-06-11
- LambdaTest 0.4.0 - 2018-03-05
- LambdaTest 0.3.1 - 2018-02-13
- LambdaTest 0.3.0 - 2017-10-30
- 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
- LambdaTest 0.2.0 - 2016-02-12
- 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
When constrained to work in a Java-only toolchain, I really miss 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.
Most important features are:
-
Write test via API (No longer required to have each test in a separate annotated method)
-
Meaningful names for tests
-
Nicely colored output per test case
-
Easy to write data-centric tests (e.g. generate as much test cases as you need programmatically, e.g. in a loop)
-
Easy to intercept exceptions with
intercept
-
Useful assertion message and difference highlighting in
expectXXX
-methods -
Opt-in to not fail fast when using
expectXXX
-methods (see more than the first assertion error) -
Easy to mark pending tests
-
Contains useful tools to work with temporary files and directories
-
Easy way to create proxies as mock dependencies
Beside this document, you can also read the JavadDoc for LambdaTest
LambdaTest is available from Maven central repository.
Maven users can use the following dependency declaration:
<dependencies>
<dependency>
<groupId>de.tototec</groupId>
<artifactId>de.tobiasroeser.lambdatest</artifactId>
<version>0.8.0</version>
<scope>test</scope>
</dependency>
<!-- If you use LambdaTest with JUnit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!-- If you use LambdaTest with TestNG -->
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>6.11</version>
<scope>test</scope>
</dependency>
<!-- If you use LambdaTest with JUnit5 / Jupiter -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.9.2</version>
<scope>test</scope>
</dependency>
</dependencies>
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 de.tobiasroeser.lambdatest.junit.FreeSpec
.
For Junit 5 (Jupiter) you inherit de.tobiasroeser.lambdatest.junit5.FreeSpec
.
For TestNG you inherit
de.tobiasroeser.lambdatest.testng.FreeSpec
.
Note
|
If you use LambdaTest with build.sc : Make mill.testng.TestNGFramework runner less verbose in Millobject test extends TestModule.TestNg {
override def forkArgs = T{ super.forkArgs() ++ Seq("-Dmill.testng.printProgress=0") }
} |
The test cases can be defined in various places.
-
in the class constructor
-
in the
protected void initTests()
method -
in a class instance initializer
Here 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);
});
}
}
// You can also define test here, to avoid
// their initialization at class construction time
@Override protected void initTests() {
test("should succeed (lazy init)", () -> {
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:
Note
|
You can run the above test directly in the LambdaTest project directory with: mvn test -Dtest=SimpleTest |
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.
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:
-
Nice output of differences between expected and actual values. Especially for string and various collection types
-
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.
Expect
-
expectNull
- Assert that a given value is null -
expectNotNull
- Assert that a given value is not null -
expectEquals
- Assert equality of two given objects or values. -
expectNotEquals
- Assert non-equality of two given objects or values. -
expectTrue
- Assert a value evaluates totrue
-
expectFalse
- Assert a value evaluates tofalse
-
expectDouble
- Assert that a given double is non-null and return an instance ofExpectDouble
with provides further checks on the actual double in a fluent API -
expectString
- Assert that a given string is non-null and return an instance ofExpectString
with provides further checks on the actual string in a fluent API -
expectCollection
- Assert that a given collection is non-null and return an instance ofExpectCollection
with provides further checks on the actual colletion in a fluent API -
expectMap
- Assert that a given map is non-null and return an instance ofExpectMap
with provides further checks on the actual map in a fluent API -
intercept
- 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 analysis
There 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
.
Note
|
If you want to use the non-fail-fast behaviour of the |
All fluet API ExpectXXX
classes support the fail-late behaviour.
To inspect and assert strings, use the class ExpectString
, which is also returned, if you use Expect.expectString
.
ExpectString
-
isEqual
-
isNotEqual
-
isEqualIgnoreCase
-
isNotEqualIgnoreCase
-
startsWith
-
StartsWithNot
-
endsWith
-
endsNotWith
-
matches
-
matchesNot
-
hasLength
-
hasLengthNot
-
isLongerThan
-
isShorterThan
-
isTrimmed
-
contains
-
containsNot
-
containsIgnoreCase
-
containsIgnoreCaseNot
To inspect and assert collections, use the class ExpectCollection
, which is also returned, if you use Expect.expectCollection
.
To inspect and assert maps, use the class ExpectMap
, which is also returned, if you use Expect.expectMap
.
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.
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
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!");
});
}
}
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 retrolambda project.
To use the latest Java7 compatible version 0.7.1 in Maven:
<dependencies>
<dependency>
<groupId>de.tototec</groupId>
<artifactId>de.tobiasroeser.lambdatest</artifactId>
<version>0.7.1</version>
<classifier>java7</classifier>
<scope>test</scope>
</dependency>
<!-- Also you need one of JUnit or TestNG, see above -->
</dependencies>
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;
}
});
}
});
}
}
LambdaTest is build with Apache Maven 3.3.1 and the polyglot-scala extension. Maven 3.5 is recommended.
mvn clean install
The built JARs file can be found in the target
directory.
This project is published under the Apache Licence Version 2.0.
Your feedback is highly appreciated. I also accept pull request.
For questions please use the Gitter chatroom. To report issues or send pull request, use GitHub.
You can also find me on Twitter as @TobiasRoeser.
If you like LambdaTest, please star it on GitHub. This will help me to set my priorities. Thanks!
-
Added support for JUnit 5 aka Jupiter.
-
Dropped released for Java 7
-
No longer use Asciidoclet to generate JavaDoc
-
Added
Expect.expectDouble
andExpectDouble
class to assert properties of double values with a fluent API. -
Improved error message in TestProxy for missing implemented proxy methods with array parameters
-
Fixed invalid Manifest entry
Import-Package
present in older releases.
-
Dropped support for Java 6. (Technically speeking, Java 6 wasn’t really supported by older versions, as a contructor of
java.lang.AssertionError
was used, which was only introduced in Java 7.) -
Proxies created with
TestProxy
gained better copy’n’paste able error message in case of unimplemented methods were call.
-
Added
Expect.expectCollection
andExpectCollection
class to assert properties of collection with a fluent API. -
TestProxy
now properly passes exceptions thrown by delegate objects. -
Improved detection of test name collisions (reported as suite warning).
-
The default reporter can now be set via
FreeSpecBase.setDefaultReporter
. -
Added
Expect.expectMap
andExpectMap
class to assert properties of maps with a fluent API.
-
Added
TestProxy
in packagede.tobiasroeser.lambdatest.proxy
to easily create mock dependencies / proxies.
-
Detect logging framework (slf4j or java.util.logging) and log test progress
-
Added
internal.Logger
andinternal.LoggerFactory
to wrap either an Slf4j-Logger or a JUL-Logger, both supporting Slf4j parameter placeholders. -
Added new
LoggingWrappingReporter
which logs to an logging frameorg and delegates all methods to an underlying Reporter. -
Changed handling of the "optional" msg-paramter in
Assert.assertXXX
andExpect.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. -
Better handle arrays with primitive types.
-
Fixed issue, where a failing assert could throw a
ClassCastExcpetion
for primitive type arrays.
-
DefaultReporter
can now hide the stacktrace -
Improved
expectEquals
message for number and arguments of different types -
Fixed inverted
expectNotNull
behaviour -
Extended test suite
-
Introduced
Reporter
interface andDefaultReporter
class -
Introduced generic base class
FreeSpecBase
to hold test framework unspecific logic -
Added
FreeSpec.section
to allow more structured tests -
Reformatted output of tests (handled now by
Reporter
) -
Added
Assert.assertNull
andAssert.assertNotNull
-
Added
Expect.expectNull
andExpect.expectNotNull
-
Generate proper OSGi manifests for all JARs/bundles
-
Fixed assert message of
Intercept.intercept(Class<T>, String, RunnableWithException)
-
Documentation improvements
-
Added a pending method with a reason parameter.
-
Added more JavaDoc comments.
-
Fixed fail late logic for Expect
-
Added new class ExpectString and Expect.expectString for fluent string assertions.
-
Fixed Assert.assertEquals for Strings, especially when expected is empty or shorter than actual.
-
Added
TempFile
, an utility class providing support to work with temporary files and directories which will be automatically cleaned up after the test case.
-
Added JUnit support. You can now use
de.tobiasroeser.lambdatest.junit.FreeSpec
for JUnit based tests in addition to the already existingde.tobiasroeser.lambdatest.testng.FreeSpec
for TestNG based tests. -
Introduced new
Assert
andExpect
classes. Expect-based asserts also support deferred exceptions. FreeSpec already integrates the setup of Expect.
-
Also release compatibility packages for older Java runtimes: Java7 and Java6. They are available via the "java7" and "java6" classifier.
-
FreeSpec.intercept now returns the intercepted exception.
-
Added FreeSpec.setRunInParallel to enable option to run tests in parallel.
-
Introduced LambdaTest interface (implemented by testng.FreeSpec) to contain the common API.
-
Rerelease of 0.0.3 under "de.tototec" groupId. You can now grab it directly from Maven Central without configuring a dedicated bintray repository.
-
Fixed issue with missapplied close of STDOUT stream
-
Fixed bug preventing from TestNG seeing pending tests as skipped
-
de.tobiasroeser.lambdatest.testng.FreeSpec class no longer inherits org.testng.Assert
-
Colored output
-
Added support to match exception messages with regex in intecept