Skip to content

Commit 6f4bb62

Browse files
garyrussellartembilan
authored andcommitted
GH-1107: Improved JUnit5 Support
Resolves #1107
1 parent 0cf87be commit 6f4bb62

File tree

6 files changed

+252
-6
lines changed

6 files changed

+252
-6
lines changed

build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,7 @@ project ('spring-kafka-test') {
266266
compile ("org.mockito:mockito-core:$mockitoVersion", optional)
267267

268268
compile ("junit:junit:$junit4Version", optional)
269+
compile "org.junit.jupiter:junit-jupiter-api:$junitJupiterVersion"
269270

270271
compile ("org.assertj:assertj-core:$assertjVersion", optional)
271272
compile ("org.apache.logging.log4j:log4j-core:$log4jVersion", optional)
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
/*
2+
* Copyright 2019 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.kafka.test.condition;
18+
19+
import java.io.IOException;
20+
import java.io.InputStream;
21+
import java.io.StringReader;
22+
import java.lang.reflect.AnnotatedElement;
23+
import java.util.Map;
24+
import java.util.Optional;
25+
import java.util.Properties;
26+
27+
import org.junit.jupiter.api.extension.AfterAllCallback;
28+
import org.junit.jupiter.api.extension.ConditionEvaluationResult;
29+
import org.junit.jupiter.api.extension.ExecutionCondition;
30+
import org.junit.jupiter.api.extension.ExtensionContext;
31+
import org.junit.jupiter.api.extension.ExtensionContext.Namespace;
32+
import org.junit.jupiter.api.extension.ExtensionContext.Store;
33+
import org.junit.jupiter.api.extension.ParameterContext;
34+
import org.junit.jupiter.api.extension.ParameterResolutionException;
35+
import org.junit.jupiter.api.extension.ParameterResolver;
36+
37+
import org.springframework.core.annotation.AnnotatedElementUtils;
38+
import org.springframework.core.io.Resource;
39+
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
40+
import org.springframework.kafka.test.EmbeddedKafkaBroker;
41+
import org.springframework.kafka.test.context.EmbeddedKafka;
42+
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
43+
import org.springframework.util.Assert;
44+
import org.springframework.util.StringUtils;
45+
46+
/**
47+
* JUnit5 condition for an embedded broker.
48+
*
49+
* @author Gary Russell
50+
* @since 2.3
51+
*
52+
*/
53+
public class EmbeddedKafkaCondition implements ExecutionCondition, AfterAllCallback, ParameterResolver {
54+
55+
private static final String EMBEDDED_BROKER = "embedded-kafka";
56+
57+
private static final ThreadLocal<EmbeddedKafkaBroker> BROKERS = new ThreadLocal<>();
58+
59+
@Override
60+
public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext)
61+
throws ParameterResolutionException {
62+
63+
return parameterContext.getParameter().getType().equals(EmbeddedKafkaBroker.class);
64+
}
65+
66+
@Override
67+
public Object resolveParameter(ParameterContext parameterContext, ExtensionContext context)
68+
throws ParameterResolutionException {
69+
70+
EmbeddedKafkaBroker broker = getBrokerFromStore(context);
71+
Assert.state(broker != null, "Could not find embedded broker instance");
72+
return broker;
73+
}
74+
75+
@Override
76+
public void afterAll(ExtensionContext context) {
77+
EmbeddedKafkaBroker broker = BROKERS.get();
78+
if (broker != null) {
79+
broker.destroy();
80+
BROKERS.remove();
81+
}
82+
}
83+
84+
@Override
85+
public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) {
86+
Optional<AnnotatedElement> element = context.getElement();
87+
if (element.isPresent()) {
88+
/*
89+
* When running in a spring test context, the EmbeddedKafkaContextCustomizer will
90+
* create the broker.
91+
*/
92+
if (AnnotatedElementUtils.findMergedAnnotation(element.get(), SpringJUnitConfig.class) == null) {
93+
EmbeddedKafka embedded = AnnotatedElementUtils.findMergedAnnotation(element.get(), EmbeddedKafka.class);
94+
if (embedded != null) {
95+
EmbeddedKafkaBroker broker = getBrokerFromStore(context);
96+
if (broker == null) {
97+
broker = createBroker(embedded);
98+
BROKERS.set(broker);
99+
getStore(context).put(EMBEDDED_BROKER, broker);
100+
}
101+
}
102+
}
103+
}
104+
return ConditionEvaluationResult.enabled("");
105+
}
106+
107+
@SuppressWarnings("unchecked")
108+
private EmbeddedKafkaBroker createBroker(EmbeddedKafka embedded) {
109+
EmbeddedKafkaBroker broker;
110+
broker = new EmbeddedKafkaBroker(embedded.count(),
111+
embedded.controlledShutdown(), embedded.topics());
112+
broker.kafkaPorts(embedded.ports());
113+
Properties properties = new Properties();
114+
115+
for (String pair : embedded.brokerProperties()) {
116+
if (!StringUtils.hasText(pair)) {
117+
continue;
118+
}
119+
try {
120+
properties.load(new StringReader(pair));
121+
}
122+
catch (Exception ex) {
123+
throw new IllegalStateException("Failed to load broker property from [" + pair + "]",
124+
ex);
125+
}
126+
}
127+
if (StringUtils.hasText(embedded.brokerPropertiesLocation())) {
128+
Resource propertiesResource = new PathMatchingResourcePatternResolver()
129+
.getResource(embedded.brokerPropertiesLocation());
130+
if (!propertiesResource.exists()) {
131+
throw new IllegalStateException(
132+
"Failed to load broker properties from [" + propertiesResource
133+
+ "]: resource does not exist.");
134+
}
135+
try (InputStream in = propertiesResource.getInputStream()) {
136+
Properties p = new Properties();
137+
p.load(in);
138+
p.forEach((key, value) -> properties.putIfAbsent(key, value));
139+
}
140+
catch (IOException ex) {
141+
throw new IllegalStateException(
142+
"Failed to load broker properties from [" + propertiesResource + "]", ex);
143+
}
144+
}
145+
broker.brokerProperties((Map<String, String>) (Map<?, ?>) properties);
146+
broker.afterPropertiesSet();
147+
return broker;
148+
}
149+
150+
private EmbeddedKafkaBroker getBrokerFromStore(ExtensionContext context) {
151+
EmbeddedKafkaBroker broker = getParentStore(context).get(EMBEDDED_BROKER, EmbeddedKafkaBroker.class) == null
152+
? getStore(context).get(EMBEDDED_BROKER, EmbeddedKafkaBroker.class)
153+
: getParentStore(context).get(EMBEDDED_BROKER, EmbeddedKafkaBroker.class);
154+
return broker;
155+
}
156+
157+
private Store getStore(ExtensionContext context) {
158+
return context.getStore(Namespace.create(getClass(), context));
159+
}
160+
161+
private Store getParentStore(ExtensionContext context) {
162+
ExtensionContext parent = context.getParent().get();
163+
return parent.getStore(Namespace.create(getClass(), parent));
164+
}
165+
166+
167+
public static EmbeddedKafkaBroker getBroker() {
168+
return BROKERS.get();
169+
}
170+
171+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
/**
2+
* Provides classes for JUnit5 conditions.
3+
*/
4+
package org.springframework.kafka.test.condition;

spring-kafka-test/src/main/java/org/springframework/kafka/test/context/EmbeddedKafka.java

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,10 @@
2323
import java.lang.annotation.RetentionPolicy;
2424
import java.lang.annotation.Target;
2525

26+
import org.junit.jupiter.api.extension.ExtendWith;
27+
2628
import org.springframework.core.annotation.AliasFor;
29+
import org.springframework.kafka.test.condition.EmbeddedKafkaCondition;
2730

2831
/**
2932
* Annotation that can be specified on a test class that runs Spring Kafka based tests.
@@ -59,6 +62,7 @@
5962
*
6063
* @see org.springframework.kafka.test.EmbeddedKafkaBroker
6164
*/
65+
@ExtendWith(EmbeddedKafkaCondition.class)
6266
@Target(ElementType.TYPE)
6367
@Retention(RetentionPolicy.RUNTIME)
6468
@Documented
@@ -108,8 +112,8 @@
108112

109113
/**
110114
* Properties in form {@literal key=value} that should be added to the broker config
111-
* before runs. Properties may contain property placeholders, e.g.
112-
* {@code delete.topic.enable=${topic.delete:true}}.
115+
* before runs. When used in a Spring test context, properties may contain property
116+
* placeholders, e.g. {@code delete.topic.enable=${topic.delete:true}}.
113117
* @return the properties to add
114118
* @see #brokerPropertiesLocation()
115119
* @see org.springframework.kafka.test.EmbeddedKafkaBroker#brokerProperties(java.util.Map)
@@ -118,10 +122,11 @@
118122

119123
/**
120124
* Spring {@code Resource} url specifying the location of properties that should be
121-
* added to the broker config. The {@code brokerPropertiesLocation} url and the
122-
* properties themselves may contain placeholders that are resolved during
123-
* initialization. Properties specified by {@link #brokerProperties()} will override
124-
* properties found in {@code brokerPropertiesLocation}.
125+
* added to the broker config. When used in a Spring test context, the
126+
* {@code brokerPropertiesLocation} url and the properties themselves may contain
127+
* placeholders that are resolved during initialization. Properties specified by
128+
* {@link #brokerProperties()} will override properties found in
129+
* {@code brokerPropertiesLocation}.
125130
* @return a {@code Resource} url specifying the location of properties to add
126131
* @see #brokerProperties()
127132
* @see org.springframework.kafka.test.EmbeddedKafkaBroker#brokerProperties(java.util.Map)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* Copyright 2019 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.kafka.test.condition;
18+
19+
import static org.assertj.core.api.Assertions.assertThat;
20+
21+
import org.junit.jupiter.api.Test;
22+
23+
import org.springframework.kafka.test.EmbeddedKafkaBroker;
24+
import org.springframework.kafka.test.context.EmbeddedKafka;
25+
26+
/**
27+
* @author Gary Russell
28+
* @since 2.3
29+
*
30+
*/
31+
@EmbeddedKafka
32+
public class EmbeddedKafkaConditionTests {
33+
34+
@Test
35+
public void test(EmbeddedKafkaBroker broker) {
36+
assertThat(broker.getBrokersAsString()).isNotNull();
37+
}
38+
39+
}

src/reference/asciidoc/testing.adoc

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,32 @@ Properties defined by `brokerProperties` override properties found in `brokerPro
263263

264264
You can use the `@EmbeddedKafka` annotation with JUnit 4 or JUnit 5.
265265

266+
[[embedded-kafka-junit5]]
267+
==== @EmbeddedKafka Annotation with JUnit5
268+
269+
Starting with version 2.3, there are two ways to use the `@EmbeddedKafka` annotation with JUnit5.
270+
When used with the `@SpringJunitConfig` annotation, the embedded broker is added to the test application context.
271+
You can auto wire the broker into your test, at the class or method level, to get the broker address list.
272+
273+
When *not* using the spring test context, the `EmbdeddedKafkaCondition` creates a broker; the condition includes a parameter resolver so you can access the broker in your test method...
274+
275+
====
276+
[source, java]
277+
----
278+
@EmbeddedKafka
279+
public class EmbeddedKafkaConditionTests {
280+
281+
@Test
282+
public void test(EmbeddedKafkaBroker broker) {
283+
String brokerList = broker.getBrokersAsString();
284+
...
285+
}
286+
287+
}
288+
----
289+
====
290+
291+
266292
==== Embedded Broker in `@SpringBootTest` Annotations
267293

268294
https://start.spring.io/[Spring Initializr] now automatically adds the `spring-kafka-test` dependency in test scope to the project configuration.

0 commit comments

Comments
 (0)