spl
spl

Reputation: 652

SpyBean not being injected everywhere

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

Answers (2)

spl
spl

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

Lucas Ross
Lucas Ross

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

Related Questions