Skip to content

Commit ca44f0b

Browse files
committed
Add IT for SpEL topics on @PulsarMessage
This commit adds a lightweight integration test nested in the DefaultTopicResolverTests which uses a small Spring context to verify actual expression evaluation. See #568
1 parent 18378a8 commit ca44f0b

File tree

3 files changed

+79
-31
lines changed

3 files changed

+79
-31
lines changed

spring-pulsar/src/main/java/org/springframework/pulsar/core/DefaultExpressionResolver.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
* {@link ConfigurableBeanFactory} capabilities to resolve expressions.
2626
*
2727
* @author Jonas Geiregat
28+
* @since 1.1.0
2829
*/
2930
public class DefaultExpressionResolver implements ExpressionResolver {
3031

@@ -40,6 +41,13 @@ public DefaultExpressionResolver(ConfigurableBeanFactory configurableBeanFactory
4041
this.configurableBeanFactory = configurableBeanFactory;
4142
}
4243

44+
/**
45+
* {@inheritDoc}
46+
* @param expression the expression to resolve (can include property placeholders and
47+
* SpEL)
48+
* @return a {@code Resolved} instance containing the resolved string value (can be
49+
* null) or an exception if the resolution failed.
50+
*/
4351
@Override
4452
public Resolved<String> resolveToString(String expression) {
4553
String placeholdersResolved = this.configurableBeanFactory.resolveEmbeddedValue(expression);
@@ -51,7 +59,7 @@ public Resolved<String> resolveToString(String expression) {
5159
return Resolved.of(null);
5260
}
5361
return Resolved
54-
.failed("The expression '%s' must resolve to a string but was: %s".formatted(expression, resolvedObj));
62+
.failed("The expression '%s' must resolve to a string but was: %s".formatted(expression, resolvedObj));
5563
}
5664

5765
}

spring-pulsar/src/main/java/org/springframework/pulsar/core/ExpressionResolver.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,18 @@
1717
package org.springframework.pulsar.core;
1818

1919
/**
20-
* Resolves expressions.
20+
* Strategy interface for resolving a value from a string expression.
2121
*
2222
* @author Jonas Geiregat
23+
* @since 1.1.0
2324
*/
2425
public interface ExpressionResolver {
2526

2627
/**
27-
* Resolve the given expression.
28+
* Resolve the given expression as a string value.
2829
* @param expression the expression to resolve
29-
* @return the resolved value as a {@code Resolved} of {@link String}
30+
* @return a {@code Resolved} instance containing the resolved string value (can be
31+
* null) or an exception if the resolution failed.
3032
*/
3133
Resolved<String> resolveToString(String expression);
3234

spring-pulsar/src/test/java/org/springframework/pulsar/core/DefaultTopicResolverTests.java

Lines changed: 65 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,9 @@
1818

1919
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
2020
import static org.junit.jupiter.params.provider.Arguments.arguments;
21-
import static org.mockito.Mockito.mock;
2221
import static org.mockito.Mockito.spy;
2322
import static org.mockito.Mockito.times;
2423
import static org.mockito.Mockito.verify;
25-
import static org.mockito.Mockito.when;
2624

2725
import java.util.stream.Stream;
2826

@@ -35,9 +33,17 @@
3533
import org.junit.jupiter.params.provider.Arguments;
3634
import org.junit.jupiter.params.provider.MethodSource;
3735

38-
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
36+
import org.springframework.beans.factory.annotation.Autowired;
37+
import org.springframework.context.annotation.Bean;
38+
import org.springframework.context.annotation.Configuration;
3939
import org.springframework.lang.Nullable;
4040
import org.springframework.pulsar.annotation.PulsarMessage;
41+
import org.springframework.pulsar.core.DefaultTopicResolverTests.TopicByAnnotatedMessageType.WithTopicExpression.WithTopicExpressionConfig;
42+
import org.springframework.test.annotation.DirtiesContext;
43+
import org.springframework.test.annotation.DirtiesContext.ClassMode;
44+
import org.springframework.test.context.ContextConfiguration;
45+
import org.springframework.test.context.TestPropertySource;
46+
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
4147

4248
/**
4349
* Unit tests for {@link DefaultTopicResolver}.
@@ -138,8 +144,6 @@ static Stream<Arguments> resolveByMessageTypeProvider() {
138144
@Nested
139145
class TopicByAnnotatedMessageType {
140146

141-
private static final String bazTopicExpression = "#{someExpression}";
142-
143147
@Test
144148
void customMappingTakesPrecedenceOverAnnotationMapping() {
145149
assertThat(resolver.resolveTopic(null, Baz.class, () -> defaultTopic).value().orElse(null))
@@ -169,29 +173,63 @@ void annotatedMessageTypeWithTopicInfo() {
169173
verify(resolver, times(1)).getAnnotatedTopicInfo(Baz.class);
170174
}
171175

172-
@Test
173-
void annotatedMessageTypeWithTopicExpressionIsResolved() {
174-
var mockExpressionResolver = mock(ExpressionResolver.class);
175-
when(mockExpressionResolver.resolveToString(bazTopicExpression)).thenReturn(Resolved.of(bazTopic));
176-
var expressionTopicResolver = new DefaultTopicResolver(mockExpressionResolver);
177-
assertThat(expressionTopicResolver.resolveTopic(null, BazWithTopicExpression.class,
178-
() -> defaultTopic).value().orElse(null))
179-
.isEqualTo(bazTopic);
180-
verify(mockExpressionResolver, times(1)).resolveToString(bazTopicExpression);
181-
}
182-
183-
@Test
184-
void deriveExpressionResolverFromBeanFactory() {
185-
var mockBeanFactory = mock(ConfigurableBeanFactory.class);
186-
var expressionTopicResolver = new DefaultTopicResolver();
187-
expressionTopicResolver.setBeanFactory(mockBeanFactory);
188-
assertThat(expressionTopicResolver)
189-
.extracting("expressionResolver")
190-
.isNotNull();
191-
}
176+
/**
177+
* Lightweight integration tests for the expression resolver functionality in
178+
* {@link DefaultTopicResolver}.
179+
* <p>
180+
* Starts up a small Spring context which in turns provides the bean factory and
181+
* expression resolver to the topic resolver.
182+
*/
183+
@Nested
184+
@SpringJUnitConfig
185+
@DirtiesContext(classMode = ClassMode.AFTER_CLASS)
186+
@ContextConfiguration(classes = WithTopicExpressionConfig.class)
187+
@TestPropertySource(properties = { "app.customPropertyTopic = my-custom-property-topic" })
188+
class WithTopicExpression {
189+
190+
// @formatter:off
191+
@Test
192+
void propertyPlaceholderExpressionIsResolved(@Autowired DefaultTopicResolver topicResolver) {
193+
assertThat(topicResolver.resolveTopic(null, MsgTypeWithTopicPropertyExpression.class, () -> defaultTopic)
194+
.value().orElse(null)).isEqualTo("my-custom-property-topic");
195+
196+
}
197+
198+
@Test
199+
void spelExpressionIsResolved(@Autowired DefaultTopicResolver topicResolver) {
200+
assertThat(topicResolver.resolveTopic(null, MsgTypeWithTopicSpELExpression.class, () -> defaultTopic)
201+
.value().orElse(null)).isEqualTo("my-custom-spel-topic");
202+
}
203+
204+
@Test
205+
void embeddedExpressionIsResolved(@Autowired DefaultTopicResolver topicResolver) {
206+
assertThat(topicResolver.resolveTopic(null, MsgTypeWithTopicEmbeddedExpression.class, () -> defaultTopic)
207+
.value().orElse(null)).isEqualTo("my-custom-property-topic".toUpperCase());
208+
}
209+
// @formatter:on
210+
211+
@Configuration(proxyBeanMethods = false)
212+
static class WithTopicExpressionConfig {
213+
214+
@Bean
215+
DefaultTopicResolver defaultTopicResolver() {
216+
return new DefaultTopicResolver();
217+
}
218+
219+
}
220+
221+
@PulsarMessage(topic = "${app.customPropertyTopic}")
222+
record MsgTypeWithTopicPropertyExpression(String value) {
223+
}
224+
225+
@PulsarMessage(topic = "#{T(java.lang.String).valueOf('my-custom-spel-topic')}")
226+
record MsgTypeWithTopicSpELExpression(String value) {
227+
}
228+
229+
@PulsarMessage(topic = "#{T(java.lang.String).valueOf('${app.customPropertyTopic}').toUpperCase()}")
230+
record MsgTypeWithTopicEmbeddedExpression(String value) {
231+
}
192232

193-
@PulsarMessage(topic = bazTopicExpression)
194-
record BazWithTopicExpression(String value) {
195233
}
196234

197235
}

0 commit comments

Comments
 (0)