Skip to content

Commit d3af209

Browse files
Igglegaryrussell
authored andcommitted
GH-1157: Defer spy stubs to original bean
Resolves #1157 CGLib proxies due to e.g. declarative transactional semantics couldn't be spied upon due to being final. Replacing the spy, which tries to hold the state itself, with a mock that delegates execution fixes this.
1 parent 29f3d0d commit d3af209

File tree

2 files changed

+130
-2
lines changed

2 files changed

+130
-2
lines changed

spring-rabbit-test/src/main/java/org/springframework/amqp/rabbit/test/RabbitListenerTestHarness.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2016-2019 the original author or authors.
2+
* Copyright 2016-2020 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -26,6 +26,7 @@
2626
import org.aopalliance.intercept.MethodInvocation;
2727
import org.apache.commons.logging.Log;
2828
import org.apache.commons.logging.LogFactory;
29+
import org.mockito.AdditionalAnswers;
2930
import org.mockito.Mockito;
3031

3132
import org.springframework.amqp.AmqpException;
@@ -37,6 +38,7 @@
3738
import org.springframework.core.annotation.AnnotationAttributes;
3839
import org.springframework.core.annotation.AnnotationUtils;
3940
import org.springframework.core.type.AnnotationMetadata;
41+
import org.springframework.test.util.AopTestUtils;
4042
import org.springframework.util.Assert;
4143
import org.springframework.util.StringUtils;
4244

@@ -48,6 +50,7 @@
4850
*
4951
* @author Gary Russell
5052
* @author Artem Bilan
53+
* @author Miguel Gross Valle
5154
*
5255
* @since 1.6
5356
*
@@ -77,7 +80,8 @@ protected void processListener(MethodRabbitListenerEndpoint endpoint, RabbitList
7780
String id = rabbitListener.id();
7881
if (StringUtils.hasText(id)) {
7982
if (this.attributes.getBoolean("spy")) {
80-
proxy = Mockito.spy(proxy);
83+
proxy = Mockito.mock(AopTestUtils.getUltimateTargetObject(proxy).getClass(),
84+
AdditionalAnswers.delegatesTo(proxy));
8185
this.listeners.put(id, proxy);
8286
}
8387
if (this.attributes.getBoolean("capture")) {
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
/*
2+
* Copyright 2020 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.amqp.rabbit.test;
18+
19+
import static org.assertj.core.api.Assertions.assertThat;
20+
import static org.mockito.ArgumentMatchers.anyString;
21+
import static org.mockito.Mockito.doAnswer;
22+
import static org.mockito.Mockito.verify;
23+
24+
import java.util.concurrent.TimeUnit;
25+
26+
import org.junit.jupiter.api.Test;
27+
28+
import org.springframework.amqp.core.AnonymousQueue;
29+
import org.springframework.amqp.core.Queue;
30+
import org.springframework.amqp.rabbit.annotation.EnableRabbit;
31+
import org.springframework.amqp.rabbit.annotation.RabbitListener;
32+
import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory;
33+
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
34+
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
35+
import org.springframework.amqp.rabbit.core.RabbitAdmin;
36+
import org.springframework.amqp.rabbit.core.RabbitTemplate;
37+
import org.springframework.amqp.rabbit.junit.RabbitAvailable;
38+
import org.springframework.amqp.rabbit.test.mockito.LatchCountDownAndCallRealMethodAnswer;
39+
import org.springframework.aop.framework.ProxyFactory;
40+
import org.springframework.beans.factory.annotation.Autowired;
41+
import org.springframework.context.annotation.Bean;
42+
import org.springframework.context.annotation.Configuration;
43+
import org.springframework.test.annotation.DirtiesContext;
44+
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
45+
46+
/**
47+
* @author Miguel Gross Valle
48+
*
49+
* @since 2.1.15
50+
*
51+
*/
52+
@SpringJUnitConfig
53+
@DirtiesContext
54+
@RabbitAvailable
55+
public class RabbitListenerProxyTest {
56+
57+
@Autowired
58+
private RabbitTemplate rabbitTemplate;
59+
60+
@Autowired
61+
private Queue queue;
62+
63+
@Autowired
64+
private RabbitListenerTestHarness harness;
65+
66+
@Test
67+
public void testProxiedListenerSpy() throws Exception {
68+
Listener listener = this.harness.getSpy("foo");
69+
assertThat(listener).isNotNull();
70+
71+
LatchCountDownAndCallRealMethodAnswer answer = new LatchCountDownAndCallRealMethodAnswer(1);
72+
doAnswer(answer).when(listener).foo(anyString());
73+
74+
this.rabbitTemplate.convertAndSend(this.queue.getName(), "foo");
75+
76+
assertThat(answer.getLatch().await(10, TimeUnit.SECONDS)).isTrue();
77+
verify(listener).foo("foo");
78+
}
79+
80+
@Configuration
81+
@EnableRabbit
82+
@RabbitListenerTest
83+
public static class Config {
84+
85+
@Bean
86+
public Listener listener() {
87+
return (Listener) new ProxyFactory(new Listener()).getProxy();
88+
}
89+
90+
@Bean
91+
public ConnectionFactory connectionFactory() {
92+
return new CachingConnectionFactory("localhost");
93+
}
94+
95+
@Bean
96+
public Queue queue() {
97+
return new AnonymousQueue();
98+
}
99+
100+
@Bean
101+
public RabbitAdmin admin(ConnectionFactory cf) {
102+
return new RabbitAdmin(cf);
103+
}
104+
105+
@Bean
106+
public RabbitTemplate template(ConnectionFactory cf) {
107+
return new RabbitTemplate(cf);
108+
}
109+
110+
@Bean
111+
public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory(ConnectionFactory cf) {
112+
SimpleRabbitListenerContainerFactory containerFactory = new SimpleRabbitListenerContainerFactory();
113+
containerFactory.setConnectionFactory(cf);
114+
return containerFactory;
115+
}
116+
}
117+
118+
public static class Listener {
119+
120+
@RabbitListener(id = "foo", queues = "#{queue.name}")
121+
public void foo(String foo) {
122+
}
123+
}
124+
}

0 commit comments

Comments
 (0)