From 0c121fa725ced31a5e2f111749cbb6deba7cc2d0 Mon Sep 17 00:00:00 2001 From: Jeroen Hoek Date: Fri, 10 Aug 2018 10:40:45 +0200 Subject: [PATCH 1/3] Update example to Java 8, and JUnit 5 --- README.md | 31 ++-- pom.xml | 145 +++++++++++++----- .../javamavenjunithelloworld/Hello.java | 12 +- .../HelloAppTest.java | 78 ++++++---- .../javamavenjunithelloworld/HelloTest.java | 21 ++- .../HelloWithTestsIT.java | 39 +++-- .../TestingSecurityManager.java | 42 +++++ 7 files changed, 255 insertions(+), 113 deletions(-) create mode 100644 src/test/java/com/example/javamavenjunithelloworld/TestingSecurityManager.java diff --git a/README.md b/README.md index 1f8ec258..06500769 100644 --- a/README.md +++ b/README.md @@ -1,31 +1,28 @@ -java-maven-junit-helloworld -=========================== +#A Java/Maven/JUnit HelloWorld example A „Hello World!” sample written in Java using Maven for the build, that showcases a few very simple tests. This example demonstrates: -* Unit tests written with [JUnit 4](http://junit.org/) -* Unit test using [PowerMockito](https://code.google.com/p/powermock/) to mock classes and test `System.exit()` -* Integration tests written with [JUnit 4](http://junit.org/) -* Integration test using [system-rules](http://www.stefan-birkner.de/system-rules/) to test `System.out` -* Code coverage reports via [Cobertura](http://cobertura.github.io/cobertura/) +* A simple Java 8 application with tests +* Unit tests written with [JUnit 5](https://junit.org/junit5/) +* Integration tests written with [JUnit 5](https://junit.org/junit5/) +* Code coverage reports via [JaCoCo](https://www.jacoco.org/jacoco/) * A Maven build that puts it all together -Running the tests ------------------ +## Running the tests * To run the unit tests, call `mvn test` * To run the integration tests as well, call `mvn verify` -* To generate (unit test) code coverage reports, call `mvn cobertura:cobertura`, and point a browser at the output in `target/site/cobertura/` +* Code coverage reports are generated when `mvn verify` (or a full `mvn clean install`) is called. + Point a browser at the output in `target/site/jacoco-both/index.html` to see the report. -Conventions ------------ +## Conventions This example follows the following basic conventions: - | unit test | integration test ---- | --- | --- -__resides in:__ | `src/test/java/*Test.java` | `src/test/java/*IT.java` -__executes in Maven phase:__ | test | verify -__handled by Maven plugin:__ | [surefire](http://maven.apache.org/surefire/maven-surefire-plugin/) | [failsafe](http://maven.apache.org/surefire/maven-failsafe-plugin/) +| | unit test | integration test | +| --- | --- | --- | +| **resides in:** | `src/test/java/*Test.java` | `src/test/java/*IT.java` | +| **executes in Maven phase:** | test | verify | +| **handled by Maven plugin:** | [surefire](http://maven.apache.org/surefire/maven-surefire-plugin/) | [failsafe](http://maven.apache.org/surefire/maven-failsafe-plugin/) | diff --git a/pom.xml b/pom.xml index 7da828e9..1c0d7dda 100644 --- a/pom.xml +++ b/pom.xml @@ -7,71 +7,79 @@ com.example java-maven-junit-helloworld - 1.0-SNAPSHOT + 2.0-SNAPSHOT jar UTF-8 UTF-8 + + + + 1.8 + ${maven.compiler.source} + + 5.2.0 + 1.2.0 + 1.3 + 2.21.0 + 0.8.1 - junit - junit - 4.11 + org.junit.jupiter + junit-jupiter-api + ${junit.jupiter.version} test - org.powermock - powermock-module-junit4 - 1.5.1 + org.junit.jupiter + junit-jupiter-params + ${junit.jupiter.version} test - org.powermock - powermock-api-mockito - 1.5.1 + org.junit.jupiter + junit-jupiter-engine + ${junit.jupiter.version} test - com.github.stefanbirkner - system-rules - 1.4.0 - test - - - - junit - junit-dep - - + org.hamcrest + hamcrest-core + ${hamcrest.version} + + + org.mockito + mockito-junit-jupiter + ${mockito.version} + org.apache.maven.plugins maven-compiler-plugin - 3.1 + 3.8.0 - 1.6 - 1.6 -Xlint + - + org.apache.maven.plugins maven-surefire-plugin - 2.16 + 2.22.0 @@ -79,7 +87,7 @@ org.apache.maven.plugins maven-failsafe-plugin - 2.16 + 2.22.0 @@ -89,18 +97,81 @@ - - - - - - org.codehaus.mojo - cobertura-maven-plugin - 2.6 + + + + + org.jacoco + jacoco-maven-plugin + ${jacoco.plugin.version} + + + jacoco-prepare-agent + + prepare-agent + + + + jacoco-prepare-agent-integration + + prepare-agent-integration + + + + jacoco-report + + report + + + + jacoco-check + + check + + + + + + + + jacoco-merge + + merge + + verify + + + + ${project.build.directory} + + *.exec + + + + ${project.build.directory}/jacoco-both.exec + + + + jacoco-integration + + report-integration + + + + jacoco-reportboth + + report + + + ${project.build.directory}/jacoco-both.exec + ${project.reporting.outputDirectory}/jacoco-both + + + - - + + diff --git a/src/main/java/com/example/javamavenjunithelloworld/Hello.java b/src/main/java/com/example/javamavenjunithelloworld/Hello.java index b3d684d5..7338993b 100644 --- a/src/main/java/com/example/javamavenjunithelloworld/Hello.java +++ b/src/main/java/com/example/javamavenjunithelloworld/Hello.java @@ -8,8 +8,10 @@ public class Hello { static final String HELLO = "Hello!"; + public static final int MAXIMUM_AMOUNT_OF_TIMES = 20; - private short times = 1; + + private int times = 1; /** * Set how many times "Hello!" should be said. @@ -19,10 +21,12 @@ public class Hello { */ public void setTimes(int times) { if (times < 0 || times > MAXIMUM_AMOUNT_OF_TIMES) { - throw new IllegalArgumentException("Parameter «times» should be a positive integer no larger than " - + MAXIMUM_AMOUNT_OF_TIMES + "."); + throw new IllegalArgumentException(String.format( + "Parameter «times» should be a positive number no larger than %d.", + MAXIMUM_AMOUNT_OF_TIMES + )); } - this.times = (short) times; + this.times = times; } /** diff --git a/src/test/java/com/example/javamavenjunithelloworld/HelloAppTest.java b/src/test/java/com/example/javamavenjunithelloworld/HelloAppTest.java index 23d93897..39fd10fc 100644 --- a/src/test/java/com/example/javamavenjunithelloworld/HelloAppTest.java +++ b/src/test/java/com/example/javamavenjunithelloworld/HelloAppTest.java @@ -1,24 +1,38 @@ package com.example.javamavenjunithelloworld; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.powermock.api.mockito.PowerMockito; -import org.powermock.core.classloader.annotations.PrepareForTest; -import org.powermock.modules.junit4.PowerMockRunner; +import com.example.javamavenjunithelloworld.TestingSecurityManager.TestExitException; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.fail; -import static org.mockito.Mockito.*; -import static org.powermock.api.mockito.PowerMockito.whenNew; /** * Unit test for HelloApp. *

* A unit test aims to test all code and code paths of a specific class. - *

- * This test uses PowerMock and Mockito to mock objects. */ -@RunWith(PowerMockRunner.class) -@PrepareForTest({System.class, HelloApp.class}) +@ExtendWith(MockitoExtension.class) public class HelloAppTest { + static SecurityManager originalSecurityManager; + + @BeforeAll + public static void setup() { + // Insert our own custom SecurityManager that throws an exception when System.exit() is called. + originalSecurityManager = System.getSecurityManager(); + System.setSecurityManager(new TestingSecurityManager()); + } + + @AfterAll + public static void tearDown() { + // Reinsert the original SecurityManager now that we are done with these tests. + System.setSecurityManager(originalSecurityManager); + } @Test public void testMain() { @@ -27,34 +41,32 @@ public void testMain() { } @Test - public void testWrongArgument() { - PowerMockito.mockStatic(System.class); - + public void testBogusArgument() { String[] args = {"bicycle"}; - HelloApp.main(args); - // Did the program exit with the expected error code? - PowerMockito.verifyStatic(only()); - System.exit(HelloApp.EXIT_STATUS_PARAMETER_NOT_UNDERSTOOD); + try { + HelloApp.main(args); + // Our custom SecurityManager should have thrown an exception when HelloApp exited. + // This means this line below cannot be reached. To make sure that our custom SecurityManager + // works as expected, we fail the test if this line is ever reached: + fail("Unreachable."); + } catch (TestExitException e) { + // Did the program exit with the expected error code? + assertThat(e.getStatus(), is(HelloApp.EXIT_STATUS_PARAMETER_NOT_UNDERSTOOD)); + } } @Test - public void testHelloError() throws Exception { - PowerMockito.mockStatic(System.class); - - // Mock Hello used by HelloApp to throw the expected exception when invoked with setTimes(5). - Hello hi = mock(Hello.class); - doThrow(new IllegalArgumentException("Nope.")).when(hi).setTimes(5); - // Sneakily insert our fake Hello class when it is created. - whenNew(Hello.class).withNoArguments().thenReturn(hi); - - // We know this will raise the expected exception, because we mocked Hello. - String[] args = {"5"}; - HelloApp.main(args); + public void testTooHighArgument() { + String[] args = {"999"}; - // Did the program exit with the expected error code? - PowerMockito.verifyStatic(only()); - System.exit(HelloApp.EXIT_STATUS_HELLO_FAILED); + try { + HelloApp.main(args); + fail("Unreachable."); + } catch (TestExitException e) { + // Did the program exit with the expected error code? + assertThat(e.getStatus(), is(HelloApp.EXIT_STATUS_HELLO_FAILED)); + } } @Test diff --git a/src/test/java/com/example/javamavenjunithelloworld/HelloTest.java b/src/test/java/com/example/javamavenjunithelloworld/HelloTest.java index e5590701..36aa9dc5 100644 --- a/src/test/java/com/example/javamavenjunithelloworld/HelloTest.java +++ b/src/test/java/com/example/javamavenjunithelloworld/HelloTest.java @@ -1,13 +1,18 @@ package com.example.javamavenjunithelloworld; -import org.junit.Test; + +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; import java.io.ByteArrayOutputStream; import java.io.OutputStream; import java.io.PrintStream; -import static org.hamcrest.CoreMatchers.*; -import static org.junit.Assert.*; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + /** * Unit test for Hello. @@ -41,15 +46,15 @@ public void testSayHelloAFewTimes() { assertThat(os.toString(), is(equalTo(goal))); } - @Test(expected = IllegalArgumentException.class) - public void testIllegalArgumentForHello21() { + @Test + public void testIllegalArgumentForHelloTooMuch() { Hello hi = new Hello(); - hi.setTimes(Hello.MAXIMUM_AMOUNT_OF_TIMES + 1); + assertThrows(IllegalArgumentException.class, () -> hi.setTimes(Hello.MAXIMUM_AMOUNT_OF_TIMES + 1)); } - @Test(expected = IllegalArgumentException.class) + @Test public void testIllegalArgumentForHelloNegative() { Hello hi = new Hello(); - hi.setTimes(-1); + assertThrows(IllegalArgumentException.class, () -> hi.setTimes(-1)); } } diff --git a/src/test/java/com/example/javamavenjunithelloworld/HelloWithTestsIT.java b/src/test/java/com/example/javamavenjunithelloworld/HelloWithTestsIT.java index 2dc2378d..3eb9f3ad 100644 --- a/src/test/java/com/example/javamavenjunithelloworld/HelloWithTestsIT.java +++ b/src/test/java/com/example/javamavenjunithelloworld/HelloWithTestsIT.java @@ -1,33 +1,44 @@ package com.example.javamavenjunithelloworld; -import org.junit.Rule; -import org.junit.Test; -import org.junit.contrib.java.lang.system.StandardOutputStreamLog; -import static org.hamcrest.CoreMatchers.*; -import static org.junit.Assert.*; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; /** * Integration test for the HelloApp program. *

* An integration test verifies the workings of a complete program, a module, or a set of dependant classes. - *

- * This integration test uses system-rules, an extension for JUnit that lets you test System.out and System.exit() - * etc.: - *

- * http://www.stefan-birkner.de/system-rules */ public class HelloWithTestsIT { + private final ByteArrayOutputStream out = new ByteArrayOutputStream(); + private final PrintStream originalOut = System.out; + + @BeforeEach + public void before() { + // By putting our own PrintStream in the place of the normal System.out, + // the output produced by the application can be verified. + System.setOut(new PrintStream(out)); + } - @Rule - public final StandardOutputStreamLog out = new StandardOutputStreamLog(); + @AfterEach + public void cleanUp() { + // Restore the original System.out to prevent weirdness in any following tests. + System.setOut(originalOut); + } @Test public void doesItSayHelloTest() { String[] args = {"1"}; HelloApp.main(args); - assertThat(out.getLog(), is(equalTo(Hello.HELLO + "\n"))); + assertThat(out.toString(), is(Hello.HELLO + "\n")); } @Test @@ -36,6 +47,6 @@ public void doesItSayHelloTest3() { HelloApp.main(args); String thrice = Hello.HELLO + "\n" + Hello.HELLO + "\n" + Hello.HELLO + "\n"; - assertThat(out.getLog(), is(equalTo(thrice))); + assertThat(out.toString(), is(thrice)); } } diff --git a/src/test/java/com/example/javamavenjunithelloworld/TestingSecurityManager.java b/src/test/java/com/example/javamavenjunithelloworld/TestingSecurityManager.java new file mode 100644 index 00000000..f4696d0c --- /dev/null +++ b/src/test/java/com/example/javamavenjunithelloworld/TestingSecurityManager.java @@ -0,0 +1,42 @@ +package com.example.javamavenjunithelloworld; + +import java.security.Permission; + +/** + * A special implementation of {@link SecurityManager} that throws a {@link RuntimeException} when + * {@link System#exit(int)} is called. This allows us to test the exit codes generated by our little application in a + * test. + */ +public class TestingSecurityManager extends SecurityManager { + @Override + public void checkPermission(Permission perm) { + // Allow everything. + } + + @Override + public void checkPermission(Permission perm, Object context) { + // Allow everything. + } + + @Override + public void checkExit(int status) { + super.checkExit(status); + // By throwing a RuntimeException, we can catch it in our tests and verify the exit code. + throw new TestExitException(status); + } + + /** + * An exception that stores the exit code for later verification. + */ + public static class TestExitException extends RuntimeException { + private final int status; + + public TestExitException(int status) { + this.status = status; + } + + public int getStatus() { + return status; + } + } +} From ef2291db9f4ae83bd4c69e0bea1f4b0f8d3e7472 Mon Sep 17 00:00:00 2001 From: Jeroen Hoek Date: Fri, 10 Aug 2018 10:44:55 +0200 Subject: [PATCH 2/3] Fix tests not using platform-specific lineendings Fixes #10. --- .../com/example/javamavenjunithelloworld/HelloTest.java | 4 ++-- .../javamavenjunithelloworld/HelloWithTestsIT.java | 9 ++++++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/test/java/com/example/javamavenjunithelloworld/HelloTest.java b/src/test/java/com/example/javamavenjunithelloworld/HelloTest.java index 36aa9dc5..7bfd1f0b 100644 --- a/src/test/java/com/example/javamavenjunithelloworld/HelloTest.java +++ b/src/test/java/com/example/javamavenjunithelloworld/HelloTest.java @@ -29,7 +29,7 @@ public void testSayHello() { Hello hi = new Hello(); hi.sayHello(stream); - assertThat(os.toString(), is(equalTo(Hello.HELLO + "\n"))); + assertThat(os.toString(), is(equalTo(String.format("%s%s", Hello.HELLO, System.lineSeparator())))); } @Test @@ -42,7 +42,7 @@ public void testSayHelloAFewTimes() { hi.sayHello(stream); // Does it say "Hello!" three times? - String goal = Hello.HELLO + "\n" + Hello.HELLO + "\n" + Hello.HELLO + "\n"; + String goal = String.format("%1$s%2$s%1$s%2$s%1$s%2$s", Hello.HELLO, System.lineSeparator()); assertThat(os.toString(), is(equalTo(goal))); } diff --git a/src/test/java/com/example/javamavenjunithelloworld/HelloWithTestsIT.java b/src/test/java/com/example/javamavenjunithelloworld/HelloWithTestsIT.java index 3eb9f3ad..a86ffd23 100644 --- a/src/test/java/com/example/javamavenjunithelloworld/HelloWithTestsIT.java +++ b/src/test/java/com/example/javamavenjunithelloworld/HelloWithTestsIT.java @@ -13,7 +13,7 @@ /** * Integration test for the HelloApp program. - *

+ *

* An integration test verifies the workings of a complete program, a module, or a set of dependant classes. */ public class HelloWithTestsIT { @@ -38,7 +38,7 @@ public void doesItSayHelloTest() { String[] args = {"1"}; HelloApp.main(args); - assertThat(out.toString(), is(Hello.HELLO + "\n")); + assertThat(out.toString(), is(String.format("%s%s", Hello.HELLO, System.lineSeparator()))); } @Test @@ -46,7 +46,10 @@ public void doesItSayHelloTest3() { String[] args = {"3"}; HelloApp.main(args); - String thrice = Hello.HELLO + "\n" + Hello.HELLO + "\n" + Hello.HELLO + "\n"; + // Hello + // Hello + // Hello + String thrice = String.format("%1$s%2$s%1$s%2$s%1$s%2$s", Hello.HELLO, System.lineSeparator()); assertThat(out.toString(), is(thrice)); } } From 9e489de2e27fae2935f4b6a8eed19b90ea4be9eb Mon Sep 17 00:00:00 2001 From: Jeroen Hoek Date: Fri, 10 Aug 2018 11:18:07 +0200 Subject: [PATCH 3/3] Fix Markdown syntax error --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 06500769..934b71e0 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -#A Java/Maven/JUnit HelloWorld example +# A Java/Maven/JUnit HelloWorld example A „Hello World!” sample written in Java using Maven for the build, that showcases a few very simple tests.