Skip to content

Commit 70205c7

Browse files
Dom Corvascempkorstanje
Dom Corvasce
andauthored
Add JUnit extension for attaching log record listener to test cases (#2558)
You can use the @WithLogRecordListener annotation to extend your test class. ``` @WithLogRecordListener class FooTest { ``` It will attach an instance of LogRecordListener to each test method. The method can access this instance by accepting an argument which is automatically resolved by the extension: @test void fooBar(LogRecordListener logRecordListener) { LogRecord logRecord = logRecordListener.getLogRecords().get(0); Closes #2546 Co-authored-by: M.P. Korstanje <[email protected]>
1 parent 6ca353d commit 70205c7

File tree

6 files changed

+138
-65
lines changed

6 files changed

+138
-65
lines changed
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package io.cucumber.core.logging;
2+
3+
import org.junit.jupiter.api.extension.AfterEachCallback;
4+
import org.junit.jupiter.api.extension.BeforeEachCallback;
5+
import org.junit.jupiter.api.extension.ExtendWith;
6+
import org.junit.jupiter.api.extension.ExtensionContext;
7+
import org.junit.jupiter.api.extension.ExtensionContext.Namespace;
8+
import org.junit.jupiter.api.extension.ParameterContext;
9+
import org.junit.jupiter.api.extension.ParameterResolutionException;
10+
import org.junit.jupiter.api.extension.ParameterResolver;
11+
12+
import java.lang.annotation.ElementType;
13+
import java.lang.annotation.Retention;
14+
import java.lang.annotation.RetentionPolicy;
15+
import java.lang.annotation.Target;
16+
17+
import static org.junit.jupiter.api.extension.ExtensionContext.Namespace.create;
18+
19+
@Target({ ElementType.TYPE, ElementType.METHOD })
20+
@Retention(RetentionPolicy.RUNTIME)
21+
@ExtendWith({ WithLogRecordListener.Extension.class })
22+
public @interface WithLogRecordListener {
23+
class Extension implements BeforeEachCallback, AfterEachCallback, ParameterResolver {
24+
private ExtensionContext.Store getContextStore(ExtensionContext context) {
25+
Namespace namespace = create(Extension.class, context.getRequiredTestMethod());
26+
return context.getStore(namespace);
27+
}
28+
29+
private LogRecordListener getLogRecordListener(ExtensionContext context) {
30+
return getContextStore(context).getOrComputeIfAbsent(LogRecordListener.class);
31+
}
32+
33+
@Override
34+
public void beforeEach(ExtensionContext extensionContext) {
35+
LogRecordListener listener = getLogRecordListener(extensionContext);
36+
LoggerFactory.addListener(listener);
37+
}
38+
39+
@Override
40+
public void afterEach(ExtensionContext extensionContext) {
41+
LogRecordListener listener = getLogRecordListener(extensionContext);
42+
LoggerFactory.removeListener(listener);
43+
}
44+
45+
@Override
46+
public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext)
47+
throws ParameterResolutionException {
48+
// @formatter:off
49+
return parameterContext.getParameter().getType() == LogRecordListener.class
50+
&& extensionContext.getTestMethod().isPresent();
51+
// @formatter:on
52+
}
53+
54+
@Override
55+
public Object resolveParameter(ParameterContext paramContext, ExtensionContext context)
56+
throws ParameterResolutionException {
57+
return getLogRecordListener(context);
58+
}
59+
60+
}
61+
62+
}

core/src/test/java/io/cucumber/core/options/CommandlineOptionsParserTest.java

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import io.cucumber.core.gherkin.Pickle;
77
import io.cucumber.core.logging.LogRecordListener;
88
import io.cucumber.core.logging.LoggerFactory;
9+
import io.cucumber.core.logging.WithLogRecordListener;
910
import io.cucumber.core.plugin.PluginFactory;
1011
import io.cucumber.core.plugin.Plugins;
1112
import io.cucumber.core.runtime.TimeServiceEventBus;
@@ -63,23 +64,12 @@
6364
import static org.junit.jupiter.api.Assertions.assertNotNull;
6465
import static org.junit.jupiter.api.Assertions.assertThrows;
6566

67+
@WithLogRecordListener
6668
class CommandlineOptionsParserTest {
6769

6870
private final Map<String, String> properties = new HashMap<>();
6971
private final ByteArrayOutputStream out = new ByteArrayOutputStream();
7072
private final CommandlineOptionsParser parser = new CommandlineOptionsParser(out);
71-
private LogRecordListener logRecordListener;
72-
73-
@BeforeEach
74-
void setup() {
75-
logRecordListener = new LogRecordListener();
76-
LoggerFactory.addListener(logRecordListener);
77-
}
78-
79-
@AfterEach
80-
void tearDown() {
81-
LoggerFactory.removeListener(logRecordListener);
82-
}
8373

8474
@Test
8575
void testParseWithObjectFactoryArgument() {
@@ -263,7 +253,7 @@ public void describeTo(Description description) {
263253
}
264254

265255
@Test
266-
void handles_null_summary_printer_backward_compatible() {
256+
void handles_null_summary_printer_backward_compatible(LogRecordListener logRecordListener) {
267257
RuntimeOptions options = parser
268258
.parse("--plugin", "null_summary", "--glue", "somewhere")
269259
.build();

core/src/test/java/io/cucumber/core/resource/ClasspathScannerTest.java

Lines changed: 3 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,18 @@
11
package io.cucumber.core.resource;
22

33
import io.cucumber.core.logging.LogRecordListener;
4-
import io.cucumber.core.logging.LoggerFactory;
4+
import io.cucumber.core.logging.WithLogRecordListener;
55
import io.cucumber.core.resource.test.ExampleClass;
66
import io.cucumber.core.resource.test.ExampleInterface;
77
import io.cucumber.core.resource.test.OtherClass;
8-
import org.hamcrest.CoreMatchers;
9-
import org.hamcrest.Matchers;
10-
import org.junit.jupiter.api.AfterEach;
11-
import org.junit.jupiter.api.BeforeEach;
128
import org.junit.jupiter.api.Test;
13-
import org.mockito.Mockito;
149

1510
import java.io.IOException;
16-
import java.net.URI;
1711
import java.net.URL;
1812
import java.net.URLConnection;
1913
import java.net.URLStreamHandler;
20-
import java.util.Arrays;
21-
import java.util.Collections;
22-
import java.util.Enumeration;
2314
import java.util.List;
24-
import java.util.Vector;
25-
import java.util.logging.Level;
26-
import java.util.logging.LogRecord;
2715

28-
import static java.util.Arrays.asList;
2916
import static java.util.Collections.enumeration;
3017
import static java.util.Collections.singletonList;
3118
import static org.hamcrest.MatcherAssert.assertThat;
@@ -36,24 +23,12 @@
3623
import static org.mockito.Mockito.mock;
3724
import static org.mockito.Mockito.when;
3825

26+
@WithLogRecordListener
3927
class ClasspathScannerTest {
4028

4129
private final ClasspathScanner scanner = new ClasspathScanner(
4230
ClasspathScannerTest.class::getClassLoader);
4331

44-
private LogRecordListener logRecordListener;
45-
46-
@BeforeEach
47-
void setup() {
48-
logRecordListener = new LogRecordListener();
49-
LoggerFactory.addListener(logRecordListener);
50-
}
51-
52-
@AfterEach
53-
void tearDown() {
54-
LoggerFactory.removeListener(logRecordListener);
55-
}
56-
5732
@Test
5833
void scanForSubClassesInPackage() {
5934
List<Class<? extends ExampleInterface>> classes = scanner.scanForSubClassesInPackage(
@@ -88,7 +63,7 @@ void scanForClassesInNonExistingPackage() {
8863
}
8964

9065
@Test
91-
void scanForResourcesInUnsupportedFileSystem() throws IOException {
66+
void scanForResourcesInUnsupportedFileSystem(LogRecordListener logRecordListener) throws IOException {
9267
ClassLoader classLoader = mock(ClassLoader.class);
9368
ClasspathScanner scanner = new ClasspathScanner(() -> classLoader);
9469
URLStreamHandler handler = new URLStreamHandler() {

core/src/test/java/io/cucumber/core/runtime/FeaturePathFeatureSupplierTest.java

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import io.cucumber.core.feature.Options;
66
import io.cucumber.core.logging.LogRecordListener;
77
import io.cucumber.core.logging.LoggerFactory;
8+
import io.cucumber.core.logging.WithLogRecordListener;
89
import org.junit.jupiter.api.AfterEach;
910
import org.junit.jupiter.api.BeforeEach;
1011
import org.junit.jupiter.api.Test;
@@ -21,25 +22,14 @@
2122
import static org.hamcrest.MatcherAssert.assertThat;
2223
import static org.junit.jupiter.api.Assertions.assertThrows;
2324

25+
@WithLogRecordListener
2426
class FeaturePathFeatureSupplierTest {
2527

2628
private final Supplier<ClassLoader> classLoader = FeaturePathFeatureSupplierTest.class::getClassLoader;
2729
private final FeatureParser parser = new FeatureParser(UUID::randomUUID);
28-
private LogRecordListener logRecordListener;
29-
30-
@BeforeEach
31-
void setup() {
32-
logRecordListener = new LogRecordListener();
33-
LoggerFactory.addListener(logRecordListener);
34-
}
35-
36-
@AfterEach
37-
void tearDown() {
38-
LoggerFactory.removeListener(logRecordListener);
39-
}
4030

4131
@Test
42-
void logs_message_if_no_features_are_found() {
32+
void logs_message_if_no_features_are_found(LogRecordListener logRecordListener) {
4333
Options featureOptions = () -> singletonList(FeaturePath.parse("classpath:io/cucumber/core/options"));
4434

4535
FeaturePathFeatureSupplier supplier = new FeaturePathFeatureSupplier(classLoader, featureOptions, parser);
@@ -49,7 +39,7 @@ void logs_message_if_no_features_are_found() {
4939
}
5040

5141
@Test
52-
void logs_message_if_no_feature_paths_are_given() {
42+
void logs_message_if_no_feature_paths_are_given(LogRecordListener logRecordListener) {
5343
Options featureOptions = Collections::emptyList;
5444

5545
FeaturePathFeatureSupplier supplier = new FeaturePathFeatureSupplier(classLoader, featureOptions, parser);

junit-platform-engine/src/test/java/io/cucumber/junit/platform/engine/DiscoverySelectorResolverTest.java

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
package io.cucumber.junit.platform.engine;
22

33
import io.cucumber.core.logging.LogRecordListener;
4-
import io.cucumber.core.logging.LoggerFactory;
54
import io.cucumber.junit.platform.engine.nofeatures.NoFeatures;
65
import org.hamcrest.CustomTypeSafeMatcher;
76
import org.hamcrest.Matcher;
8-
import org.junit.jupiter.api.AfterEach;
97
import org.junit.jupiter.api.BeforeEach;
108
import org.junit.jupiter.api.Test;
119
import org.junit.platform.engine.ConfigurationParameters;
@@ -51,25 +49,19 @@
5149
import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId;
5250
import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUri;
5351

52+
@WithLogRecordListener
5453
class DiscoverySelectorResolverTest {
5554

5655
private final DiscoverySelectorResolver resolver = new DiscoverySelectorResolver();
57-
private final LogRecordListener logRecordListener = new LogRecordListener();
5856
private CucumberEngineDescriptor testDescriptor;
5957

6058
@BeforeEach
6159
void before() {
62-
LoggerFactory.addListener(logRecordListener);
6360
UniqueId id = UniqueId.forEngine(new CucumberTestEngine().getId());
6461
testDescriptor = new CucumberEngineDescriptor(id);
6562
assertEquals(0, testDescriptor.getChildren().size());
6663
}
6764

68-
@AfterEach
69-
void after() {
70-
LoggerFactory.removeListener(logRecordListener);
71-
}
72-
7365
@Test
7466
void resolveRequestWithClasspathResourceSelector() {
7567
DiscoverySelector resource = selectClasspathResource("io/cucumber/junit/platform/engine/single.feature");
@@ -409,7 +401,7 @@ void resolveRequestWithClassSelector() {
409401
}
410402

411403
@Test
412-
void resolveRequestWithClassSelectorShouldLogWarnIfNoFeaturesFound() {
404+
void resolveRequestWithClassSelectorShouldLogWarnIfNoFeaturesFound(LogRecordListener logRecordListener) {
413405
DiscoverySelector resource = selectClass(NoFeatures.class);
414406
EngineDiscoveryRequest discoveryRequest = new SelectorRequest(resource);
415407
resolver.resolveSelectors(discoveryRequest, testDescriptor);
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package io.cucumber.junit.platform.engine;
2+
3+
import io.cucumber.core.logging.LogRecordListener;
4+
import io.cucumber.core.logging.LoggerFactory;
5+
import org.junit.jupiter.api.extension.AfterEachCallback;
6+
import org.junit.jupiter.api.extension.BeforeEachCallback;
7+
import org.junit.jupiter.api.extension.ExtendWith;
8+
import org.junit.jupiter.api.extension.ExtensionContext;
9+
import org.junit.jupiter.api.extension.ExtensionContext.Namespace;
10+
import org.junit.jupiter.api.extension.ParameterContext;
11+
import org.junit.jupiter.api.extension.ParameterResolutionException;
12+
import org.junit.jupiter.api.extension.ParameterResolver;
13+
14+
import java.lang.annotation.ElementType;
15+
import java.lang.annotation.Retention;
16+
import java.lang.annotation.RetentionPolicy;
17+
import java.lang.annotation.Target;
18+
19+
import static org.junit.jupiter.api.extension.ExtensionContext.Namespace.create;
20+
21+
@Target({ ElementType.TYPE, ElementType.METHOD })
22+
@Retention(RetentionPolicy.RUNTIME)
23+
@ExtendWith({ WithLogRecordListener.Extension.class })
24+
public @interface WithLogRecordListener {
25+
class Extension implements BeforeEachCallback, AfterEachCallback, ParameterResolver {
26+
private ExtensionContext.Store getContextStore(ExtensionContext context) {
27+
Namespace namespace = create(Extension.class, context.getRequiredTestMethod());
28+
return context.getStore(namespace);
29+
}
30+
31+
private LogRecordListener getLogRecordListener(ExtensionContext context) {
32+
return getContextStore(context).getOrComputeIfAbsent(LogRecordListener.class);
33+
}
34+
35+
@Override
36+
public void beforeEach(ExtensionContext extensionContext) {
37+
LogRecordListener listener = getLogRecordListener(extensionContext);
38+
LoggerFactory.addListener(listener);
39+
}
40+
41+
@Override
42+
public void afterEach(ExtensionContext extensionContext) {
43+
LogRecordListener listener = getLogRecordListener(extensionContext);
44+
LoggerFactory.removeListener(listener);
45+
}
46+
47+
@Override
48+
public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext)
49+
throws ParameterResolutionException {
50+
// @formatter:off
51+
return parameterContext.getParameter().getType() == LogRecordListener.class
52+
&& extensionContext.getTestMethod().isPresent();
53+
// @formatter:on
54+
}
55+
56+
@Override
57+
public Object resolveParameter(ParameterContext paramContext, ExtensionContext context)
58+
throws ParameterResolutionException {
59+
return getLogRecordListener(context);
60+
}
61+
62+
}
63+
64+
}

0 commit comments

Comments
 (0)