Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/lefou/lambdatest

LambdaTest - Lambda-enabled functional testing API on top of JUnit and TestNG
https://github.com/lefou/lambdatest

functional-programming java java5 java8 junit junit5 specification testing testng

Last synced: about 1 month ago
JSON representation

LambdaTest - Lambda-enabled functional testing API on top of JUnit and TestNG

Awesome Lists containing this project

README

        

= 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:

* 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

== 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:

[source,xml,subs="attributes,verbatim"]
----


de.tototec
de.tobiasroeser.lambdatest
{lambdatestversion}
test



junit
junit
{junitversion}
test



org.testng
testng
{testngversion}
test



org.junit.jupiter
junit-jupiter
{jupiterversion}
test

----

== 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`].

[NOTE]
--
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"]
----
object test extends TestModule.TestNg {
override def forkArgs = T{ super.forkArgs() ++ Seq("-Dmill.testng.printProgress=0") }
}
----
--

== Writing tests with Lambda Test

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`.

[source,java]
----
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:

image:Screenshot_SimpleTest.jpg[]

[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.

== 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:

* 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.

.Selected static methods of `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 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 API
* `expectString` - 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 API
* `expectCollection` - 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 API
* `expectMap` - 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 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 `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>, List)` or the more convenient and compact `TestProxy.proxy(Object...)` method.

// TODO: example
.Example Test using `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:

[source,xml,subs="attributes,verbatim"]
----


de.tototec
de.tobiasroeser.lambdatest
0.7.1
java7
test

----

Instead of Java 8 Closures, you have to create anonymous classes.

[source,java]
----
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.

.Build LambdaTest from source
----
mvn clean install
----

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.

.Creating `pom.xml` files
----
mvn -Pgen-pom-xml initialize
----

.Deleting generated `pom.xml` files
----
mvn -Pgen-pom-xml clean
----

== 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

* Added support for JUnit 5 aka Jupiter.
* Dropped released for Java 7
* No longer use Asciidoclet to generate JavaDoc

=== LambdaTest 0.7.1 - 2021-06-28

* Added support for lazily initialized tests (with `initTests()`)

=== LambdaTest 0.7.0 - 2019-01-10

* Added `Expect.expectDouble` and `ExpectDouble` class to assert properties of double values with a fluent API.
* Improved error message in TestProxy for missing implemented proxy methods with array parameters

=== LambdaTest 0.6.2 - 2018-08-01

* Fixed invalid Manifest entry `Import-Package` present in older releases.

=== LambdaTest 0.6.1 - 2018-07-24

* 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.

=== LambdaTest 0.6.0 - 2018-06-22

* Added `Expect.expectCollection` and `ExpectCollection` 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` and `ExpectMap` class to assert properties of maps with a fluent API.

=== LambdaTest 0.5.0 - 2018-06-11

* Added `TestProxy` in package `de.tobiasroeser.lambdatest.proxy` to easily create mock dependencies / proxies.

=== LambdaTest 0.4.0 - 2018-03-05

* Detect logging framework (slf4j or java.util.logging) and log test progress
* Added `internal.Logger` and `internal.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` 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.
* Better handle arrays with primitive types.
* Fixed issue, where a failing assert could throw a `ClassCastExcpetion` for
primitive type arrays.

=== LambdaTest 0.3.1 - 2018-02-13

* `DefaultReporter` can now hide the stacktrace
* Improved `expectEquals` message for number and arguments of different types
* Fixed inverted `expectNotNull` behaviour
* Extended test suite

=== LambdaTest 0.3.0 - 2017-10-30

* Introduced `Reporter` interface and `DefaultReporter` 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` and `Assert.assertNotNull`
* Added `Expect.expectNull` and `Expect.expectNotNull`
* Generate proper OSGi manifests for all JARs/bundles
* Fixed assert message of `Intercept.intercept(Class, String, RunnableWithException)`
* Documentation improvements

=== LambdaTest 0.2.4 - 2016-08-03

* Added a pending method with a reason parameter.
* Added more JavaDoc comments.

=== LambdaTest 0.2.3 - 2016-05-10

* Fixed fail late logic for Expect
* Added new class ExpectString and Expect.expectString for fluent string assertions.

=== LambdaTest 0.2.2 - 2016-05-03

* Fixed Assert.assertEquals for Strings, especially when expected is empty or shorter than actual.

=== LambdaTest 0.2.1 - 2016-03-04

* Added `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

* Added JUnit support.
You can now use `de.tobiasroeser.lambdatest.junit.FreeSpec` for JUnit based tests in addition to the already existing `de.tobiasroeser.lambdatest.testng.FreeSpec` for TestNG based tests.
* Introduced new `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

* 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 LambdaTest 0.0.3 - 2014-11-29

* Rerelease of 0.0.3 under "de.tototec" groupId.
You can now grab it directly from Maven Central without configuring a dedicated bintray repository.

=== LambdaTest 0.0.3 - 2014-11-16

* 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

=== LambdaTest 0.0.2 - 2014-10-18

* Colored output
* Added support to match exception messages with regex in intecept

=== LambdaTest 0.0.1 - 2014-10-15

* First release