JUnit5 extension that verify written document matches with approved one.

It checks that everything written during test is identical to the approved content.

Creating a test using ApprovalsExtension

This is an example to create a simple test using ApprovalsExtension.

You have to write a class and add register an ApprovalsExtension attribute using RegisterExtension annotation. This extension will check that everything wrote using write method has not changed since the last execution.

Test example using ApprovalsExtension
package com.github.docastest.samples.justone;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import com.github.docastest.doctesting.junitextension.ApprovalsExtension;
import com.github.docastest.doctesting.junitextension.SimpleApprovalsExtension;
public class OneTest {
    @RegisterExtension
    static final ApprovalsExtension doc = new SimpleApprovalsExtension();

    @Test
    public void test_A() {
        doc.write("In my *test*");
    }

}

When executing test method test_A, the following text is generated.

[#com_github_docastest_samples_justone_onetest_test_a]
= Test A

In my *test*

If this content is identical to the _OneTest.test_A.approved.adoc, then the test is a success. Otherwise, test fails and the generated text is written to the _OneTest.test_A.received.adoc file. So we can compare those two files to see what has changed.

File name and title come from method name. The chapter content contains what was written using write.

Files are stored in src/test/docs/com/github/docastest/samples/justone directory which contains:

  • _OneTest.approved.adoc

  • _OneTest.test_A.approved.adoc

There is one file per test and one file for the class.

Using displayName

You can use DisplayName annotation to customize test title

Test example using DisplayName
@DisplayName("Title for the document")
class UsingDisplayNameTest {
    @RegisterExtension
    static final ApprovalsExtension doc = new SimpleApprovalsExtension();

    @Test
    @DisplayName("Title for this test")
    public void test_A() {
        doc.write("In my *test*");
    }
}
Generated file with DisplayName content as title
[#com_github_docastest_doctesting_junitextension_usingdisplaynametest_test_a]
= Title for this test

In my *test*

Hide title

You can hide a test title adding the NoTitle annotation to the test. It’s usefull to reuse content of a test in different place without keeping the original structure. You also want to separate the content of one chapter through different test but display them under only title.

Test example using hiding title
public class MyTestWithoutTitleOnOneTest {
    @RegisterExtension
    static final ApprovalsExtension doc = new SimpleApprovalsExtension();

    @Test
    public void intro() {
        doc.write("First line");
    }

    @Test
    @NoTitle
    public void my_method() {
        doc.write("My content without title");
    }

    @Test
    public void conclusion() {
        doc.write("Last line");
    }
}

The file generated from the method with NoTitle annotation has no header. On the final rendering, it’s like it was part of the previous chapter.

Rendering of the result

My test without title on one test

Intro

First line

My content without title

Conclusion

Last line

Using a custom writer

It’s possible to give a specific DocWriter to ApprovalsExtension and modify what it will be written in the final document.

Test example using ApprovalsExtension
class MyCustomWriterTest {

    @RegisterExtension
    static final ApprovalsExtension doc = new ApprovalsExtension(
            new DocWriter() {
                @Override
                public void write(String... texts) {
                    super.write(texts);
                    super.write(" // Add a comment after each call to write");
                }

                @Override
                public String formatOutput(String title, Method testMethod) {

                    return "// Add an header to the document\n"
                            + super.formatOutput(title + ": Custom title", testMethod);
                }
            }
    );

    @Test
    public void test_A() {
        doc.write("In my *test*");
    }

}

When executing test method test_A, a file _MyCustomWriterTest.test_A.approved.adoc is generated and contains the following text

// Add an header to the document

[#com_github_docastest_samples_mycustomwritertest_test_a]
= Test A: Custom title

In my *test* // Add a comment after each call to write

Using a custom formatter

It’s possible to give a specific Formatter to ApprovalsExtension and change the rendering for some instructions or create another formatter.

Test example using ApprovalsExtension
class MyCustomFormatterTest {

    @RegisterExtension
    static final ApprovalsExtension doc = new ApprovalsExtension(
            new DocWriter(
                    new AsciidocFormatter() {
                        @Override
                        /// Add the word `Warning` before the message.
                        public String warning(String message) {
                            return super.warning("Warning: " + message);
                        }
                    })
    );

    @Test
    public void test_A() {
        doc.write(doc.getFormatter().warning("My custom warning."));
    }

}

When executing test method test_A, a file _MyCustomFormatterTest.test_A.approved.adoc is generated and contains the following text

[#com_github_docastest_samples_mycustomformattertest_test_a]
= Test A


[WARNING]
====
Warning: My custom warning.
====

Nested class

Nested class can be used to organize tests. Each nested class create a nested title.

Test example using nested class
/**
 * Demo of a simple usage to generate documentation.
 */
public class DemoNestedTest {
    @RegisterExtension
    static final ApprovalsExtension writer = new SimpleApprovalsExtension();

    /**
     * Document of Addition operations.
     */
    @Nested
    class Adding {

        @Test
        @DisplayName("Adding 2 simple numbers")
        public void should_be_5_when_adding_2_and_3() {
            writer.write(String.format("%d + %d = %d", 2, 3, 2 + 3));
        }

        /**
         * A nested test.
         */
        @Test
        @DisplayName("Adding 3 simple numbers")
        public void should_be_9_when_adding_2_3_and_4() {
            writer.write(String.format("%d + %d + %d = %d", 2, 3, 4, 2 + 3 + 4));
        }
    }

    @Nested
    class Multiply {
        @Test
        @DisplayName("Multiply 2 simple numbers")
        public void should_be_12_when_multiply_4_and_3() {
            writer.write(String.format("%d * %d = %d", 4, 3, 4 * 3));
        }
    }
}

Generated files in com/github/docastest/doctesting/junitextension:

  • _DemoNestedTest.Adding.should_be_5_when_adding_2_and_3.approved.adoc

  • _DemoNestedTest.Adding.should_be_9_when_adding_2_3_and_4.approved.adoc

  • _DemoNestedTest.Multiply.should_be_12_when_multiply_4_and_3.approved.adoc

  • _DemoNestedTest.approved.adoc

Document generated
[#com_github_docastest_doctesting_junitextension_demonestedtest]
= Demo nested test

Demo of a simple usage to generate documentation.

[#com_github_docastest_doctesting_junitextension_demonestedtest_adding]
== Adding

Document of Addition operations.

include::_DemoNestedTest.Adding.should_be_5_when_adding_2_and_3.approved.adoc[leveloffset=+2]

include::_DemoNestedTest.Adding.should_be_9_when_adding_2_3_and_4.approved.adoc[leveloffset=+2]

[#com_github_docastest_doctesting_junitextension_demonestedtest_multiply]
== Multiply

include::_DemoNestedTest.Multiply.should_be_12_when_multiply_4_and_3.approved.adoc[leveloffset=+2]

Final rendering

Rendering

Demo nested test

Demo of a simple usage to generate documentation.

Adding

Document of Addition operations.

Adding 2 simple numbers

2 + 3 = 5

Adding 3 simple numbers

A nested test.

2 + 3 + 4 = 9

Multiply
Multiply 2 simple numbers

4 * 3 = 12

Document with all tests in a testclass

At the end of a test, a file is created including files generated on each test.

ApprovalsExtension must be static to be able to run AfterAll callback.

Test example used to generate class document
class MyTest {
    @RegisterExtension
    static final ApprovalsExtension doc = new SimpleApprovalsExtension();

    @Test
    public void test_A() {
        doc.write("In my *test*");
    }

}

Class MyTest is in package com.github.docastest.samples.

Document generated
ifndef::ROOT_PATH[:ROOT_PATH: ../../../..]

[#com_github_docastest_samples_mytest]
= My test

include::_MyTest.test_A.approved.adoc[leveloffset=+1]

Final rendering

My test

Test A

In my test

Failing test output

When a test fails, the error is written in the final document. It’s help to understand and investigate on the problem.

When the test fails, the reason (exception) is written into the generated document.

Test example used to generate class document
class FailingTest {
    @RegisterExtension
    static final ApprovalsExtension doc = new SimpleApprovalsExtension();

    @Test
    public void failing_test() {
        doc.write("Some information before failure.", "", "");
        fail("Problem on the test, it fails.");
        doc.write("Information added after failure are not in the final document.", "");
    }
}
Document generated (exception stack trace is truncated)
ifndef::ROOT_PATH[:ROOT_PATH: ../../../..]

[#com_github_docastest_samples_failingtest_failing_test]
= Failing test

Some information before failure.



*Error generating documentation*
----
org.opentest4j.AssertionFailedError: Problem on the test, it fails.
	at org.junit.jupiter.api.AssertionUtils.fail(AssertionUtils.java:39)
	at org.junit.jupiter.api.Assertions.fail(Assertions.java:134)
	at com.github.docastest.samples.FailingTest.failing_test(FailingTest.java:23)
	...

----

Final rendering

Failing test

Some information before failure.

Error generating documentation

org.opentest4j.AssertionFailedError: Problem on the test, it fails.
	at org.junit.jupiter.api.AssertionUtils.fail(AssertionUtils.java:39)
	at org.junit.jupiter.api.Assertions.fail(Assertions.java:134)
	at com.github.docastest.samples.FailingTest.failing_test(FailingTest.java:23)
	...

A received file is produced for the class containing the failing test. This file includes received files if they exist. It is thus possible to visualize the received file in the file with other methods.

Document generated
ifndef::ROOT_PATH[:ROOT_PATH: ../../../..]

[#com_github_docastest_samples_failingtest]
= Failing test

include::_FailingTest.failing_test.received.adoc[leveloffset=+1]