diff --git a/.github/workflows/build-and-deploy.yml b/.github/workflows/build-and-deploy.yml
index 33bb524..a5c4a74 100644
--- a/.github/workflows/build-and-deploy.yml
+++ b/.github/workflows/build-and-deploy.yml
@@ -1,4 +1,4 @@
-name: Build with Maven and deploy to Sonatype OSS repo
+name: Build and deploy to Sonatype OSS repo
on:
push:
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index d542ffe..9f5843e 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -1,4 +1,4 @@
-name: Build with Maven
+name: Build
on:
push:
diff --git a/README.md b/README.md
index 68fabb6..5c9dddf 100644
--- a/README.md
+++ b/README.md
@@ -1,50 +1,63 @@
# log-capture
[](https://dmtech.de/)
-[![Build Status](https://travis-ci.org/dm-drogeriemarkt/log-capture.svg?branch=master)](https://travis-ci.org/dm-drogeriemarkt/log-capture)
+[![Apache License 2](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
+[![Build Status](https://github.com/dm-drogeriemarkt/log-capture/actions/workflows/build.yml/badge.svg?branch=master)](https://github.com/dm-drogeriemarkt/log-capture/actions?query=branch%3Amaster)
+[![Maven Central](https://maven-badges.herokuapp.com/maven-central/de.dm.infrastructure/log-capture/badge.svg)](https://maven-badges.herokuapp.com/maven-central/de.dm.infrastructure/log-capture)
Simple assertions for log messages. See [Examples](#examples).
+**Note that** log-capture asserts evaluated log statements. That means it depends on a logging implementation (*logback*), but works with any logging facade (*slf4j* and others)
+
```java
+var name="world";
+log.info("hello {}", name);
+log.warn("bye {}", name);
+
logCapture.assertLogged(
- info("hello world"),
- warn("bye world")
- );
+ info("hello world"),
+ warn("bye world")
+);
```
It's even simple when there's more than just the message and level to assert:
```java
-logCapture.assertLogged(
- info("hello world",
- logger("com.acme.helloworld.WorldGreeter")
- )
- warn("bye world",
- exception().expectedType(WorldNotFoundException.class)
- )
- );
+logCapture.assertLoggedInOrder(
+ info("hello world",
+ logger("com.acme.helloworld.WorldGreeter"))
+ warn("bye world",
+ exception().expectedType(WorldNotFoundException.class))
+);
```
**Table of Contents**
* [Usage](#usage)
- * [Maven](#maven)
- * [Examples](#examples)
- * [Unit Test Example:](#unit-test-example)
- * [Integration Test Example:](#integration-test-example)
- * [Example with MDC](#example-with-mdc)
- * [More Examples](#more-examples)
+ * [Maven](#maven)
+ * [Additional matchers](#additional-matchers)
+ * [MDC content](#mdc-content)
+ * [Exceptions](#exceptions)
+ * [Markers](#markers)
+ * [Logger name](#logger-name)
+ * [Examples](#examples)
+ * [Unit Test Example:](#unit-test-example)
+ * [Integration Test Example:](#integration-test-example)
+ * [Example with additional MDC matcher](#example-with-additional-mdc-matcher)
+ * [More Examples](#more-examples)
* [Usage outside of JUnit 5 (Cucumber example)](#usage-outside-of-junit-5-cucumber-example)
- * [Cucumber example](#cucumber-example)
- * [Cucumber feature file](#cucumber-feature-file)
+ * [Cucumber example](#cucumber-example)
+ * [Cucumber feature file](#cucumber-feature-file)
* [Changes](#changes)
- * [3.3.0](#330)
- * [3.2.1](#321)
- * [3.2.0](#320)
- * [3.1.0](#310)
- * [3.0.0](#300)
- * [2.0.1](#201)
- * [Updating from Version 1.x.x to 2.x.x](#updating-from-version-1xx-to-2xx)
+ * [3.4.0](#340)
+ * [3.3.0](#330)
+ * [3.2.1](#321)
+ * [3.2.0](#320)
+ * [3.1.0](#310)
+ * [3.0.0](#300)
+ * [2.0.1](#201)
+ * [Updating from Version 1.x.x to 2.x.x](#updating-from-version-1xx-to-2xx)
+ * [Updating from Version 3.2.x or lower to Version 3.3.x or higher](#updating-from-version-32x-or-lower-to-version-33x-or-higher)
## Usage
@@ -53,15 +66,62 @@ logCapture.assertLogged(
Add log-capture as a test dependency to your project. If you use Maven, add this to your pom.xml:
```xml
-
de.dm.infrastructure
log-capture
- 3.3.0
+ 3.4.0
test
```
+### Additional matchers
+
+The default matchers can match level and/or message. But there are additional matchers for log messages. See also [the detailed example below](#example-with-additional-mdc-matcher)
+
+#### MDC content
+
+```java
+MDC.put("key", "value");
+log.info("did something");
+
+logCapture.info("did something", mdc("key", "value"));`
+```
+
+#### Exceptions
+
+```java
+log.warn("oh no!",
+ new IllegalArgumentException("shame on you!",
+ new NullPointerException("never use null")));
+
+logCapture.assertLogged(
+ warn("oh no!",
+ exception()
+ .expectedType(IllegalArgumentException.class)
+ .expectedCause(exception()
+ .expectedType(NullPointerException.class)
+ .expectedMessageRegex("never use null")
+ .build())
+ .build()
+ ));
+```
+
+#### Markers
+
+```java
+log.info(MarkerFactory.getMarker("my-marker"), "hello with marker");
+
+logCapture.info("hello with marker", marker("my-marker"));
+```
+
+#### Logger name
+
+```java
+log.info("did something");
+
+logCapture.info("did something", logger("com.acme.foo"));
+```
+
### Examples
#### Unit Test Example:
@@ -88,21 +148,21 @@ public class MyUnitTest {
//assert that the messages have been logged
//expected log message is a regular expression
logCapture.assertLogged(
- info("^something interesting$"),
- error("terrible")
+ info("^something interesting$"),
+ error("terrible")
);
//is the same assertion, but also asserts the order of these log messages
logCapture.assertLoggedInOrder(
- info("^something interesting$"),
- error("terrible")
+ info("^something interesting$"),
+ error("terrible")
);
//assert that no log message containing "something unwanted" with any log level exists
//and that no log message with level DEBUG exists
logCapture.assertNotLogged(
- any("something unwanted"),
- debug()
+ any("something unwanted"),
+ debug()
);
}
}
@@ -135,14 +195,14 @@ public class MyIntegrationTest {
log.error("something terrible");
logCapture.assertLogged(
- info("^something interesting$"),
- info("^start of info from utility.that.logs"),
- error("terrible"));
+ info("^something interesting$"),
+ info("^start of info from utility.that.logs"),
+ error("terrible"));
}
}
```
-#### Example with MDC
+#### Example with additional MDC matcher
```Java
package my.company.application;
@@ -168,26 +228,25 @@ public class MyUnitTest {
// asserts my_mdc_key for both message and other_mdc_key for the second one
logCapture
- .with(mdc("my_mdc_key", "^this is the MDC value$"))
- .assertLoggedInOrder(
- info("information attached"),
- info("additional MDC information attached",
- mdc("other_mdc_key", "^this is the other MDC value$")));
+ .with(mdc("my_mdc_key", "^this is the MDC value$"))
+ .assertLoggedInOrder(
+ info("information attached"),
+ info("additional MDC information attached",
+ mdc("other_mdc_key", "^this is the other MDC value$")));
}
}
```
-If assertLogged fails because the message is correct, but the MDC value is wrong, the assertion error will contain the
-actual MDC values of the last captured log event where the log message and level matched.
+If assertLogged fails because the message is correct, but the MDC value is wrong, the assertion error will contain the actual MDC values of the last captured log event where the log message and level matched.
This can be useful for debugging purposes. For example, this test code:
```java
MDC.put("my_mdc_key","this is the wrong MDC value");
- MDC.put("other_mdc_key","this is the other MDC value");
- log.info("this message has some MDC information attached");
+ MDC.put("other_mdc_key","this is the other MDC value");
+ log.info("this message has some MDC information attached");
- logCapture.assertLogged().info("information attached",
+ logCapture.assertLogged().info("information attached",
mdc("my_mdc_key","^something expected that never occurs$"),
mdc("other_mdc_key","^this is the other MDC value$"));
```
@@ -208,8 +267,7 @@ See [ReadableApiTest.java](src/test/java/com/example/app/ReadableApiTest.java) f
## Usage outside of JUnit 5 (Cucumber example)
-If you intend to use LogCapture outside of a JUnit test, you cannot rely on JUnit's `@RegisterExtension` annotation and
-must call LogCapture's `addAppenderAndSetLogLevelToTrace()` and `removeAppenderAndResetLogLevel()` methods manually.
+If you intend to use LogCapture outside of a JUnit test, you cannot rely on JUnit's `@RegisterExtension` annotation and must call LogCapture's `addAppenderAndSetLogLevelToTrace()` and `removeAppenderAndResetLogLevel()` methods manually.
Be aware that this will still cause JUnit to be a dependency.
@@ -231,12 +289,18 @@ And with MDC logging context
## Changes
+### 3.4.0
+
+* Added `assertNotLogged(...)` for asserting that no matching log message has been logged
+* Added more factory methods for `warn(...)`, `error(...)` as a shortcut to ignore the message when matching
+* Added `any(...)` factory method for matching any log message regardless of level
+
### 3.3.0
* Introduced a new fluent API with
* better readability
* extensible log message assertions (to assert attached Exceptions, Markers and LoggerName beyond MDC content)
-* Deprecated the old API (will be removed in 4.0)
+* Deprecated the old API (will be removed in 4.0, [how to update](#updating-from-version-32x-or-lower-to-version-33x-or-higher))
### 3.2.1
@@ -269,3 +333,50 @@ Fixed a bug where multiline log messages (for example Messages that contain a st
* `LogCapture.forIntegrationTest(...)` has been replaced with `LogCapture.forPackages(...)`
* `logCapture.addAppender()` has been replaced with `logCapture.addAppenderAndSetLogLevelToTrace()`
* `logCapture.removeAppender()` has been replaced with `logCapture.removeAppenderAndResetLogLevel()`
+
+### Updating from Version 3.2.x or lower to Version 3.3.x or higher
+
+Since the old API has been deprecated in 3.3.0 in preparation for 4.0, existing assertions should be replaced. So for example:
+
+```java
+...
+
+import static ch.qos.logback.classic.Level.INFO;
+import static de.dm.infrastructure.logcapture.ExpectedMdcEntry.withMdc;
+
+...
+
+// plain assertion
+logCapture.assertLogged(INFO, "Something happened.");
+// assertion with MDC
+logCapture.assertLogged(INFO, "Something with MDC content",
+ withMdc("bookingNumber", "1234"));
+// in-order assertion
+logCapture
+ .assertLogged(INFO, "Step 1")
+ .thenLogged(INFO, "Step 2");
+
+```
+
+needs to be replaced with:
+
+```java
+...
+
+import static de.dm.infrastructure.logcapture.ExpectedMdcEntry.mdc;
+import static de.dm.infrastructure.logcapture.LogExpectation.info;
+
+...
+
+// plain assertion
+logCapture.assertLogged(info("Something happened."));
+// assertion with MDC
+logCapture.assertLogged(info("Something with MDC content",
+ mdc("rabattUpdate", "CapturableHeadline")));
+// in-order assertion
+ogCapture.assertLoggedInOrder(
+ info("Step 1")
+ info("Step 2")
+);
+
+```
diff --git a/pom.xml b/pom.xml
index e65874d..b9abab2 100644
--- a/pom.xml
+++ b/pom.xml
@@ -8,7 +8,7 @@
${project.version}
Log Capture
- Makes it possible to capture logs and assert if things have been logged
+ assertions for logging with logback
https://github.com/dm-drogeriemarkt/log-capture
@@ -29,7 +29,7 @@
- 3.3.0-SNAPSHOT
+ 3.4.0-SNAPSHOT
1.8
1.18.20
diff --git a/src/main/java/de/dm/infrastructure/logcapture/LogAsserter.java b/src/main/java/de/dm/infrastructure/logcapture/LogAsserter.java
index 6e64454..92211dd 100644
--- a/src/main/java/de/dm/infrastructure/logcapture/LogAsserter.java
+++ b/src/main/java/de/dm/infrastructure/logcapture/LogAsserter.java
@@ -3,7 +3,6 @@
import ch.qos.logback.classic.Level;
import lombok.RequiredArgsConstructor;
-import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
@@ -27,17 +26,18 @@ public class LogAsserter {
/**
* assert that multiple log messages have been logged in any order
*
- * @param logExpectation description of an expected log message
- * @param moreLogExpectations more descriptions of expected log messages
+ * @param logExpectations descriptions of expected log messages
*
* @return asserter that can be used to check if anything else has been logged
*
* @throws AssertionError if any of the expected log message has not been logged or matching is imprecise (in case multiple expectations match the same message)
+ * @throws IllegalArgumentException if less than two LogExpectations are provided
*/
- public NothingElseLoggedAsserter assertLoggedInAnyOrder(LogExpectation logExpectation, LogExpectation... moreLogExpectations) {
- LinkedList logExpectations = new LinkedList<>();
- logExpectations.add(logExpectation);
- logExpectations.addAll(Arrays.asList(moreLogExpectations));
+ public NothingElseLoggedAsserter assertLoggedInAnyOrder(LogExpectation... logExpectations) {
+ if (logExpectations.length < 2) {
+ throw new IllegalArgumentException("at least 2 LogExpectations are required for assertLoggedInAnyOrder(). Found " +
+ (logExpectations.length == 1 ? logExpectations[0] : "none"));
+ }
Map matches = new HashMap<>();
@@ -49,52 +49,68 @@ public NothingElseLoggedAsserter assertLoggedInAnyOrder(LogExpectation logExpect
"Imprecise matching: Two log expectations have matched the same message. " +
"Use more precise matching or in-order matching. " +
"(First match: %s | Second match: %s",
- getLevelAndRegexExpectation(previousMatch.level, previousMatch.regex, previousMatch.logEventMatchers), getLevelAndRegexExpectation(assertion.level, assertion.regex, assertion.logEventMatchers)));
+ getLevelAndRegexExpectation(previousMatch.level, previousMatch.regex, previousMatch.logEventMatchers),
+ getLevelAndRegexExpectation(assertion.level, assertion.regex, assertion.logEventMatchers)));
}
matches.put(lastCapturedLogEvent.lastAssertedLogMessageIndex, assertion);
}
- return new NothingElseLoggedAsserter(logExpectations.size());
+ return new NothingElseLoggedAsserter(logExpectations.length);
}
+ /**
+ * assert a single multiple message has been logged
+ *
+ * @param logExpectation descriptions of expected log message
+ *
+ * @return asserter that can be used to check if anything else has been logged
+ *
+ * @throws AssertionError if the expected log message has not been logged
+ */
+ public NothingElseLoggedAsserter assertLogged(LogExpectation logExpectation) {
+ assertCapturedNext(logExpectation.level, logExpectation.regex, Optional.empty(), logExpectation.logEventMatchers);
+ return new NothingElseLoggedAsserter(1);
+ }
+
+
/**
* assert that multiple log messages have been logged in an expected order
*
- * @param logExpectation description of the first expected log message
- * @param nextLogExpectation description of the second expected log message
- * @param moreLogExpectations descriptions of further expected log messages, in order
+ * @param logExpectations descriptions of expected log messages, in order
*
* @return asserter that can be used to check if anything else has been logged
*
* @throws AssertionError if any of the expected log message has not been logged or have been logged in the wrong order
+ * @throws IllegalArgumentException if less than two LogExpectations are provided
*/
- public NothingElseLoggedAsserter assertLoggedInOrder(LogExpectation logExpectation, LogExpectation nextLogExpectation, LogExpectation... moreLogExpectations) {
- LinkedList logExpectations = new LinkedList<>();
- logExpectations.add(logExpectation);
- logExpectations.add(nextLogExpectation);
- logExpectations.addAll(Arrays.asList(moreLogExpectations));
+ public NothingElseLoggedAsserter assertLoggedInOrder(LogExpectation... logExpectations) {
+ if (logExpectations.length < 2) {
+ throw new IllegalArgumentException("at least 2 LogExpectations are required for assertLoggedInOrder(). Found " +
+ (logExpectations.length == 1 ? logExpectations[0] : "none"));
+ }
Optional lastCapturedLogEvent = Optional.empty();
for (LogExpectation assertion : logExpectations) {
lastCapturedLogEvent = Optional.of(assertCapturedNext(assertion.level, assertion.regex, lastCapturedLogEvent, assertion.logEventMatchers));
}
- return new NothingElseLoggedAsserter(logExpectations.size());
+ return new NothingElseLoggedAsserter(logExpectations.length);
}
/**
* assert that no matching log-message was logged
*
- * @param logExpectation description of an expected log message
- * @param moreLogExpectations more descriptions of expected log messages
+ * @param logExpectations descriptions of log messages that should not occur
*
* @throws AssertionError if any of the expected log message has been logged
+ * @throws IllegalArgumentException if no LogExpectation is provided
*/
- public void assertNotLogged(LogExpectation logExpectation, LogExpectation... moreLogExpectations) {
- LinkedList logExpectations = new LinkedList<>();
- logExpectations.add(logExpectation);
- logExpectations.addAll(Arrays.asList(moreLogExpectations));
+ public void assertNotLogged(LogExpectation... logExpectations) {
+ if (logExpectations.length < 1) {
+ throw new IllegalArgumentException("at least one LogExpectation is required for assertNotLogged(). Found none");
+ }
+
for (LogExpectation assertion : logExpectations) {
assertNotCaptured(assertion.level, assertion.regex, assertion.logEventMatchers);
@@ -199,11 +215,10 @@ private static boolean eventMatchesPattern(LoggedEvent event, Pattern pattern) {
return pattern.matcher(event.getFormattedMessage()).matches();
}
- private static boolean eventMatchesLevel(LoggedEvent event, Optional level) {
- if (!level.isPresent()) {
- return true;
- }
- return event.getLevel().equals(level.get());
+ private static boolean eventMatchesLevel(LoggedEvent event, Optional expectedLevel) {
+ return expectedLevel
+ .map(expected -> event.getLevel().equals(expected))
+ .orElse(true);
}
static boolean isMatchedByAll(LoggedEvent loggedEvent, List extends LogEventMatcher> logEventMatchers) {
@@ -213,6 +228,7 @@ static boolean isMatchedByAll(LoggedEvent loggedEvent, List extends LogEventMa
return logEventMatchers.stream().allMatch(matcher -> matcher.matches(loggedEvent));
}
+ @SuppressWarnings("squid:S1192") // a constant for "Level: " is not helpful
private static String getLevelAndRegexExpectation(Optional level, Optional regex) {
if (!level.isPresent() && !regex.isPresent()) {
@@ -221,14 +237,14 @@ private static String getLevelAndRegexExpectation(Optional level, Optiona
if (level.isPresent() && regex.isPresent()) {
return "Level: " + level.get() + ", Regex: \"" + regex.get() + "\"";
}
- return (level.isPresent() ? "Level: " + level.get() : "") +
- (regex.isPresent() ? "Regex: \"" + regex.get() + "\"" : "");
+ return (level.map(value -> "Level: " + value).orElse("")) +
+ (regex.map(s -> "Regex: \"" + s + "\"").orElse(""));
}
private static String getLevelAndRegexExpectation(Optional level, Optional regex, List matchers) {
String matchersText = "";
if (!matchers.isEmpty()) {
- matchersText = ", with matchers: " + matchers.stream().map(logEventMatcher -> logEventMatcher.getMatchingErrorMessage()).collect(Collectors.joining(", "));
+ matchersText = ", with matchers: " + matchers.stream().map(LogEventMatcher::getMatchingErrorMessage).collect(Collectors.joining(", "));
}
if (!level.isPresent() && !regex.isPresent()) {
return "" + matchersText;
@@ -236,8 +252,8 @@ private static String getLevelAndRegexExpectation(Optional level, Optiona
if (level.isPresent() && regex.isPresent()) {
return "Level: " + level.get() + ", Regex: \"" + regex.get() + "\"" + matchersText;
}
- return (level.isPresent() ? "Level: " + level.get() : "") +
- (regex.isPresent() ? "Regex: \"" + regex.get() + "\"" : "") + matchersText;
+ return (level.map(value -> "Level: " + value).orElse("")) +
+ (regex.map(s -> "Regex: \"" + s + "\"").orElse("")) + matchersText;
}
}
diff --git a/src/main/java/de/dm/infrastructure/logcapture/LogCapture.java b/src/main/java/de/dm/infrastructure/logcapture/LogCapture.java
index e48f37d..199f1ae 100644
--- a/src/main/java/de/dm/infrastructure/logcapture/LogCapture.java
+++ b/src/main/java/de/dm/infrastructure/logcapture/LogCapture.java
@@ -179,7 +179,7 @@ private LastCapturedLogEvent assertLogged(Level level, String regex, LastCapture
*/
public LogAsserter.NothingElseLoggedAsserter assertLogged(LogExpectation logExpectation) {
return new LogAsserter(capturingAppender, new LinkedList<>())
- .assertLoggedInAnyOrder(logExpectation);
+ .assertLogged(logExpectation);
}
/**
@@ -190,14 +190,14 @@ public LogAsserter.NothingElseLoggedAsserter assertLogged(LogExpectation logExpe
* logCapture.assertNothingMatchingLogged(info("hello world"));
* }
*
- * @param logExpectation description of the not expected log message
- * @param moreLogExpectations more log event matchers describing expectations
+ * @param logExpectations descriptions of log messages that should not occur
*
* @throws AssertionError if the expected log message has not been logged
+ * @throws IllegalArgumentException if no LogExpectation is provided
*/
- public void assertNotLogged(LogExpectation logExpectation, LogExpectation... moreLogExpectations) {
+ public void assertNotLogged(LogExpectation... logExpectations) {
new LogAsserter(capturingAppender, new LinkedList<>())
- .assertNotLogged(logExpectation, moreLogExpectations);
+ .assertNotLogged(logExpectations);
}
/**
@@ -211,16 +211,16 @@ public void assertNotLogged(LogExpectation logExpectation, LogExpectation... mor
* ));
* }
*
- * @param logExpectation description of an expected log message
- * @param moreLogExpectations more descriptions of expected log messages
+ * @param logExpectations more descriptions of expected log messages
*
* @return asserter that can be used to check if anything else has been logged
*
* @throws AssertionError if any of the expected log message has not been logged or matching is imprecise (in case multiple expectations match the same message)
+ * @throws IllegalArgumentException if less than two LogExpectations are provided
*/
- public LogAsserter.NothingElseLoggedAsserter assertLoggedInAnyOrder(LogExpectation logExpectation, LogExpectation... moreLogExpectations) {
+ public LogAsserter.NothingElseLoggedAsserter assertLoggedInAnyOrder(LogExpectation... logExpectations) {
return new LogAsserter(capturingAppender, new LinkedList<>())
- .assertLoggedInAnyOrder(logExpectation, moreLogExpectations);
+ .assertLoggedInAnyOrder(logExpectations);
}
/**
@@ -235,17 +235,16 @@ public LogAsserter.NothingElseLoggedAsserter assertLoggedInAnyOrder(LogExpectati
* ));
* }
*
- * @param logExpectation description of the first expected log message
- * @param nextLogExpectation description of the second expected log message
- * @param moreLogExpectations descriptions of further expected log messages, in order
+ * @param logExpectations descriptions of expected log messages, in order
*
* @return asserter that can be used to check if anything else has been logged
*
* @throws AssertionError if any of the expected log message has not been logged or have been logged in the wrong order
+ * @throws IllegalArgumentException if less than two LogExpectations are provided
*/
- public LogAsserter.NothingElseLoggedAsserter assertLoggedInOrder(LogExpectation logExpectation, LogExpectation nextLogExpectation, LogExpectation... moreLogExpectations) {
+ public LogAsserter.NothingElseLoggedAsserter assertLoggedInOrder(LogExpectation... logExpectations) {
return new LogAsserter(capturingAppender, new LinkedList<>())
- .assertLoggedInOrder(logExpectation, nextLogExpectation, moreLogExpectations);
+ .assertLoggedInOrder(logExpectations);
}
@@ -263,16 +262,18 @@ public LogAsserter.NothingElseLoggedAsserter assertLoggedInOrder(LogExpectation
* ));
* }
*
- * @param logEventMatcher log event matcher describing expectations
- * @param moreLogEventMatchers more log event matchers describing expectations
+ * @param logEventMatchers log event matchers describing expectations
*
* @return an asserter to assert log messages with the described additional expectations
+ *
+ * @throws IllegalArgumentException if no LogEventMatcher is provided
*/
- public LogAsserter with(LogEventMatcher logEventMatcher, LogEventMatcher... moreLogEventMatchers) {
- LinkedList logEventMatchers = new LinkedList<>();
- logEventMatchers.add(logEventMatcher);
- logEventMatchers.addAll(Arrays.asList(moreLogEventMatchers));
- return new LogAsserter(capturingAppender, logEventMatchers);
+ public LogAsserter with(LogEventMatcher... logEventMatchers) {
+ if (logEventMatchers.length < 1) {
+ throw new IllegalArgumentException("with() needs at least one LogEventMatcher");
+ }
+
+ return new LogAsserter(capturingAppender, Arrays.asList(logEventMatchers));
}
/**
diff --git a/src/main/java/de/dm/infrastructure/logcapture/LogExpectation.java b/src/main/java/de/dm/infrastructure/logcapture/LogExpectation.java
index a73a585..87a507f 100644
--- a/src/main/java/de/dm/infrastructure/logcapture/LogExpectation.java
+++ b/src/main/java/de/dm/infrastructure/logcapture/LogExpectation.java
@@ -1,6 +1,7 @@
package de.dm.infrastructure.logcapture;
import ch.qos.logback.classic.Level;
+import lombok.ToString;
import java.util.Arrays;
import java.util.List;
@@ -9,8 +10,11 @@
/**
* class for describing an expected log message
*/
+@ToString(onlyExplicitlyIncluded = true)
public final class LogExpectation {
+ @ToString.Include
final Optional level;
+ @ToString.Include
final Optional regex;
final List logEventMatchers;
diff --git a/src/test/java/com/example/app/ReadableApiTest.java b/src/test/java/com/example/app/ReadableApiTest.java
index fae3730..2d2fa24 100644
--- a/src/test/java/com/example/app/ReadableApiTest.java
+++ b/src/test/java/com/example/app/ReadableApiTest.java
@@ -25,6 +25,7 @@
import static org.junit.jupiter.api.Assertions.assertThrows;
@Slf4j
+@SuppressWarnings("java:S5778") //this rule does not increase the clarity of these tests
class ReadableApiTest {
@RegisterExtension
LogCapture logCapture = LogCapture.forCurrentPackage();
@@ -36,6 +37,50 @@ void basicReadableApiSucceeds() {
logCapture.assertLogged(info("hello world"));
}
+ @Test
+ void varargsAssertionsRequireLogExpectations() {
+ assertThat(
+ assertThrows(IllegalArgumentException.class, () ->
+ logCapture.assertLoggedInAnyOrder()
+ ))
+ .hasMessage("at least 2 LogExpectations are required for assertLoggedInAnyOrder(). Found none");
+
+ assertThat(
+ assertThrows(IllegalArgumentException.class, () ->
+ logCapture.assertLoggedInAnyOrder(info("Hello world"))
+ ))
+ .hasMessageMatching("at least 2 LogExpectations are required for assertLoggedInAnyOrder\\(\\)\\. Found .*Hello world.*");
+
+ assertThat(
+ assertThrows(IllegalArgumentException.class, () ->
+ logCapture.assertLoggedInOrder()
+ ))
+ .hasMessage("at least 2 LogExpectations are required for assertLoggedInOrder(). Found none");
+
+ assertThat(
+ assertThrows(IllegalArgumentException.class, () ->
+ logCapture.assertLoggedInOrder(info("Hello world"))
+ ))
+ .hasMessageMatching("at least 2 LogExpectations are required for assertLoggedInOrder\\(\\)\\. Found .*Hello world.*");
+
+ assertThat(
+ assertThrows(IllegalArgumentException.class, () ->
+ logCapture.assertNotLogged()
+ ))
+ .hasMessageMatching("at least one LogExpectation is required for assertNotLogged\\(\\)\\. Found none");
+ }
+
+ @Test
+ void withRequiresAtLeastOneMatcher() {
+ assertThat(
+ assertThrows(IllegalArgumentException.class, () ->
+ logCapture
+ .with()
+ .assertLogged(info("Hello world"))
+ ))
+ .hasMessage("with() needs at least one LogEventMatcher");
+ }
+
@Test
void allLevelsWork() {
log.error("first error");
@@ -615,5 +660,27 @@ void nothingElseLoggedOutOfOrderFails() {
assertThat(assertionError).hasMessage("There have been other log messages than the asserted ones.");
}
+
+ @Test
+ void nothingElseLoggedSingleLogMessageSucceeds() {
+ log.info("hello world");
+
+ logCapture
+ .assertLogged(info("hello world"))
+ .assertNothingElseLogged();
+ }
+
+ @Test
+ void nothingElseLoggedSingleLogMessageFails() {
+ log.info("hello world");
+ log.info("hello universe");
+
+ AssertionError assertionError = assertThrows(AssertionError.class, () ->
+ logCapture
+ .assertLogged(info("hello world"))
+ .assertNothingElseLogged());
+
+ assertThat(assertionError).hasMessage("There have been other log messages than the asserted ones.");
+ }
}
}