diff --git a/testing-modules/junit-4/pom.xml b/testing-modules/junit-4/pom.xml
new file mode 100644
index 0000000000..272a9380b5
--- /dev/null
+++ b/testing-modules/junit-4/pom.xml
@@ -0,0 +1,18 @@
+
+
+ 4.0.0
+ junit-4
+ 1.0-SNAPSHOT
+ junit-4
+ JUnit 4 Topics
+
+
+ com.baeldung
+ parent-modules
+ 1.0.0-SNAPSHOT
+ ../../
+
+
+
diff --git a/testing-modules/junit-4/src/main/resources/logback.xml b/testing-modules/junit-4/src/main/resources/logback.xml
new file mode 100644
index 0000000000..7d900d8ea8
--- /dev/null
+++ b/testing-modules/junit-4/src/main/resources/logback.xml
@@ -0,0 +1,13 @@
+
+
+
+
+ %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/testing-modules/junit-4/src/test/java/com/baeldung/rules/MessageLogger.java b/testing-modules/junit-4/src/test/java/com/baeldung/rules/MessageLogger.java
new file mode 100644
index 0000000000..45afc067d5
--- /dev/null
+++ b/testing-modules/junit-4/src/test/java/com/baeldung/rules/MessageLogger.java
@@ -0,0 +1,34 @@
+package com.baeldung.rules;
+
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class MessageLogger implements TestRule {
+
+ private static final Logger LOG = LoggerFactory.getLogger(MessageLogger.class);
+
+ private String message;
+
+ public MessageLogger(String message) {
+ this.message = message;
+ }
+
+ @Override
+ public Statement apply(final Statement base, Description description) {
+ return new Statement() {
+ @Override
+ public void evaluate() throws Throwable {
+ try {
+ LOG.info("Starting: {}", message);
+ base.evaluate();
+ } finally {
+ LOG.info("Finished: {}", message);
+ }
+ }
+ };
+ }
+
+}
diff --git a/testing-modules/junit-4/src/test/java/com/baeldung/rules/RuleChainUnitTest.java b/testing-modules/junit-4/src/test/java/com/baeldung/rules/RuleChainUnitTest.java
new file mode 100644
index 0000000000..c66a62a3d0
--- /dev/null
+++ b/testing-modules/junit-4/src/test/java/com/baeldung/rules/RuleChainUnitTest.java
@@ -0,0 +1,21 @@
+package com.baeldung.rules;
+
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.RuleChain;
+
+public class RuleChainUnitTest {
+
+ @Rule
+ public RuleChain chain = RuleChain.outerRule(new MessageLogger("First rule"))
+ .around(new MessageLogger("Second rule"))
+ .around(new MessageLogger("Third rule"));
+
+ @Test
+ public void givenRuleChain_whenTestRuns_thenChainOrderApplied() {
+ assertTrue(true);
+ }
+
+}
diff --git a/testing-modules/junit-4/src/test/java/com/baeldung/rules/RulesUnitTest.java b/testing-modules/junit-4/src/test/java/com/baeldung/rules/RulesUnitTest.java
new file mode 100644
index 0000000000..cc1cfa376d
--- /dev/null
+++ b/testing-modules/junit-4/src/test/java/com/baeldung/rules/RulesUnitTest.java
@@ -0,0 +1,88 @@
+package com.baeldung.rules;
+
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.isA;
+import static org.hamcrest.Matchers.not;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+
+import org.junit.Ignore;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.DisableOnDebug;
+import org.junit.rules.ErrorCollector;
+import org.junit.rules.ExpectedException;
+import org.junit.rules.TemporaryFolder;
+import org.junit.rules.TestName;
+import org.junit.rules.Timeout;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class RulesUnitTest {
+
+ private static final Logger LOG = LoggerFactory.getLogger(RulesUnitTest.class);
+
+ @Rule
+ public TemporaryFolder tmpFolder = new TemporaryFolder();
+
+ @Rule
+ public final ExpectedException thrown = ExpectedException.none();
+
+ @Rule
+ public TestName name = new TestName();
+
+ @Rule
+ public Timeout globalTimeout = Timeout.seconds(10);
+
+ @Rule
+ public final ErrorCollector errorCollector = new ErrorCollector();
+
+ @Rule
+ public DisableOnDebug disableTimeout = new DisableOnDebug(Timeout.seconds(30));
+
+ @Rule
+ public TestMethodNameLogger testLogger = new TestMethodNameLogger();
+
+ @Test
+ public void givenTempFolderRule_whenNewFile_thenFileIsCreated() throws IOException {
+ File testFile = tmpFolder.newFile("test-file.txt");
+
+ assertTrue("The file should have been created: ", testFile.isFile());
+ assertEquals("Temp folder and test file should match: ", tmpFolder.getRoot(), testFile.getParentFile());
+ }
+
+ @Test
+ public void givenIllegalArgument_whenExceptionThrown_thenMessageAndCauseMatches() {
+ thrown.expect(IllegalArgumentException.class);
+ thrown.expectCause(isA(NullPointerException.class));
+ thrown.expectMessage("This is illegal");
+
+ throw new IllegalArgumentException("This is illegal", new NullPointerException());
+ }
+
+ @Test
+ public void givenAddition_whenPrintingTestName_thenTestNameIsDisplayed() {
+ LOG.info("Executing: {}", name.getMethodName());
+ assertEquals("givenAddition_whenPrintingTestName_thenTestNameIsDisplayed", name.getMethodName());
+ }
+
+ @Ignore
+ @Test
+ public void givenLongRunningTest_whenTimout_thenTestFails() throws InterruptedException {
+ TimeUnit.SECONDS.sleep(20);
+ }
+
+ @Ignore
+ @Test
+ public void givenMultipleErrors_whenTestRuns_thenCollectorReportsErrors() {
+ errorCollector.addError(new Throwable("First thing went wrong!"));
+ errorCollector.addError(new Throwable("Another thing went wrong!"));
+
+ errorCollector.checkThat("Hello World", not(containsString("ERROR!")));
+ }
+
+}
diff --git a/testing-modules/junit-4/src/test/java/com/baeldung/rules/TestMethodNameLogger.java b/testing-modules/junit-4/src/test/java/com/baeldung/rules/TestMethodNameLogger.java
new file mode 100644
index 0000000000..df4de56131
--- /dev/null
+++ b/testing-modules/junit-4/src/test/java/com/baeldung/rules/TestMethodNameLogger.java
@@ -0,0 +1,32 @@
+package com.baeldung.rules;
+
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class TestMethodNameLogger implements TestRule {
+
+ private static final Logger LOG = LoggerFactory.getLogger(TestMethodNameLogger.class);
+
+ @Override
+ public Statement apply(Statement base, Description description) {
+ logInfo("Before test", description);
+ try {
+ return new Statement() {
+ @Override
+ public void evaluate() throws Throwable {
+ base.evaluate();
+ }
+ };
+ } finally {
+ logInfo("After test", description);
+ }
+ }
+
+ private void logInfo(String msg, Description description) {
+ LOG.info(msg + description.getMethodName());
+ }
+
+}
diff --git a/testing-modules/junit-4/src/test/java/com/baeldung/rules/VerifierRuleUnitTest.java b/testing-modules/junit-4/src/test/java/com/baeldung/rules/VerifierRuleUnitTest.java
new file mode 100644
index 0000000000..ffc3f611c3
--- /dev/null
+++ b/testing-modules/junit-4/src/test/java/com/baeldung/rules/VerifierRuleUnitTest.java
@@ -0,0 +1,30 @@
+package com.baeldung.rules;
+
+import static org.junit.Assert.assertFalse;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.Verifier;
+
+public class VerifierRuleUnitTest {
+
+ private List messageLog = new ArrayList();
+
+ @Rule
+ public Verifier verifier = new Verifier() {
+ @Override
+ public void verify() {
+ assertFalse("Message Log is not Empty!", messageLog.isEmpty());
+ }
+ };
+
+ @Test
+ public void givenNewMessage_whenVerified_thenMessageLogNotEmpty() {
+ // ...
+ messageLog.add("There is a new message!");
+ }
+
+}
diff --git a/testing-modules/pom.xml b/testing-modules/pom.xml
index 8d40c668c0..e15fdb4a37 100644
--- a/testing-modules/pom.xml
+++ b/testing-modules/pom.xml
@@ -37,5 +37,6 @@
easymock
junit-5-advanced
xmlunit-2
+ junit-4