Reputation: 652
I'm having difficulty getting a spy bean into my ApplicationContext. I have a bean called utilities of type Utilities:
@Component("utilities")
public class Utilities {
<snip>
/**
* Returns a random int. This is provided mostly for testing mock-ability
*
* @return a random integer
*/
public int getRandom() {
return (int) (Math.random() * Integer.MAX_VALUE);
}
}
And it's used from within a class indirectly referenced by my Spring Integration flow.
Then I have this Jupiter test:
@TestInstance(Lifecycle.PER_CLASS)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@ExtendWith(SpringExtension.class)
@ContextConfiguration( classes = {
XmlLocations.class,
VisitorManager.class,
Utilities.class,
UnixTimeChannel.class
})
@WebMvcTest
//@TestExecutionListeners( { MockitoTestExecutionListener.class })
public class FullIntegrationTest {
@Autowired
private MockMvc mvc;
@SpyBean
private Utilities utilities;
private ClientAndServer mockServer;
private static final int MOCK_SERVER_PORT = 9089;
@BeforeAll
public void setUpBeforeClass() {
Mockito.when(utilities.getRandom()).thenReturn(Integer.MAX_VALUE);
mockServer = ClientAndServer.startClientAndServer(MOCK_SERVER_PORT);
RestAssuredMockMvc.mockMvc(mvc);
(new MockServerPingInit()).initializeExpectations(mockServer);
(new MockServerFullIntegrationInit()).initializeExpectations(mockServer);
}
@Test
public void t00200_IncomingMessage() {
RestAssuredMockMvc.given()
.queryParam("example", "example")
.when()
.request("POST", "/api/v1/incoming")
.then()
.statusCode(equalTo(200));
}
<snip>
But even though I create the spy bean and use a when/thenReturn on it it doesn't float off into my application context waiting to be called and return its mocked random value.
I know that the method utilities.getRandom() is getting called because I can place a breakpoint on it and debug the test, and it hits the getRandom method, but when I try to add a spy bean as shown above and mock out the getRandom to return a fixed value for testing the breakpoints still hits and so I can tell the real method not the mock is being called.
I've tried putting the when/thenReturn inside the test as well in case it's too early but it doesn't help.
Clearly I'm doing something wrong, possibly conceptually wrong. Halp!
Upvotes: 3
Views: 10173
Reputation: 652
Okay, Thank you all for attempts to help. Without meaning to frustrate, posting configuration and flow won't help I think, because of what I've found below:
There was an exception on closer inspection:
org.springframework.expression.AccessException: Could not resolve bean reference against BeanFactory
The reference in question was to a method inside the utilities which I have used @SpyBean on:
<int:transformer
expression="@utilities.asMap('licence_id', headers[licenceId], 'message', 'Delivered: ' + headers[confirmedMessage], 'secured_session_id', headers[visitorSession].getSecureSessionId())" />
It's not a separate ApplicationContext but rather SpEL won't accept the spy bean because the reference has changed or similar.
So, I've left the utilities alone and retrofitted another bean internal to that to generate the numbers, and used SpyBean on that. Now Spring Integration/SpEL is happy again because the utilities bean it's working with is correct and the mocking happens internal to that bean and transparently to SpEL.
@Component
public class RandomSupplier implements Supplier<Double> {
@Override
public Double get() {
return Math.random();
}
}
public class FullIntegrationTest {
@Autowired
private MockMvc mvc;
@SpyBean
private RandomSupplier randomSupplier;
@Autowired // This is only necessary for the toy test below
private Utilities utilities;
@BeforeEach
public void setupAfterInit() {
Mockito.when(randomSupplier.get()).thenReturn(0.5);
}
@Test
public void t0() throws IOException {
System.out.println(utilities.getRandom());
}
...
Now Spring Integration/SpEL is happy again because the utilities bean it's working is correct and the mocking happens internal to that bean.
Three lessons: Don't spy beans directly referenced in SpEL inside a Spring Integration Flow; Read the logs; You can never have enough indirection :)
Upvotes: 1
Reputation: 1089
I attempted to recreate your problem with a minimal configuration:
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = {Ctx.class})
public class XTest {
@SpyBean
private Random random1;
@Autowired private Supplier<Integer> intSupplier;
@Test
public void test() {
Mockito.when(random1.nextInt()).thenReturn(Integer.MAX_VALUE);
int i = intSupplier.get();
System.out.println("i=" + i);
}
@Configuration
public static class Ctx {
@Bean
static Random random1() {
return ThreadLocalRandom.current();
}
@Bean
static Supplier<Integer> intSupplier(Random random1) {
return random1::nextInt;
}
}
}
And as expected it prints
i=2147483647
So, there must be an issue with your runtime configuration... Could you share that? I’m guessing spring-integration is using another ApplicationContext. I know this isn't an answer and I will delete it if it doesn't help.
Upvotes: 1