lefou / LambdaTest

LambdaTest - Lambda-enabled functional testing API on top of JUnit and TestNG
Apache License 2.0
9 stars 1 forks source link
functional-programming java java5 java8 junit junit5 specification testing testng

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

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

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:

.Selected static methods of Expect

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

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

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

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

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