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.
ApprovalsExtensionpackage 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
@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*");
}
}
[#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.
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.
ApprovalsExtensionclass 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.
ApprovalsExtensionclass 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.
/**
* 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
[#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.
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.
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.
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.", "");
}
}
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.
ifndef::ROOT_PATH[:ROOT_PATH: ../../../..] [#com_github_docastest_samples_failingtest] = Failing test include::_FailingTest.failing_test.received.adoc[leveloffset=+1]