Micronaut: Mock Factory-created Beans in Spock

emazzotta :

I need to perform remoting calls from Micronaut to a Spring Application. In order to create the necessary beans I created a Factory:

@Factory
public class RemotingConfig {

  @Bean
  @Singleton
  public OfferLeadService offerLeadService(@Value("${offer.server.remoting.base.url}") 
                                                 String offerRemotingBaseUrl) {
    HttpInvokerProxyFactoryBean invoker = new HttpInvokerProxyFactoryBean();
    invoker.setHttpInvokerRequestExecutor(new SimpleHttpInvokerRequestExecutor());
    invoker.setServiceUrl(offerRemotingBaseUrl + OfferLeadService.URI);
    invoker.setServiceInterface(OfferLeadService.class);
    invoker.afterPropertiesSet();
    return (OfferLeadService) invoker.getObject();
  }

  @Bean
  @Singleton
  public APIKeyService apiKeyService(@Value("${offer.server.remoting.base.url}") 
                                           String offerRemotingBaseUrl) {
    HttpInvokerProxyFactoryBean invoker = new HttpInvokerProxyFactoryBean();
    invoker.setHttpInvokerRequestExecutor(new SimpleHttpInvokerRequestExecutor());
    invoker.setServiceUrl(offerRemotingBaseUrl + APIKeyService.URI);
    invoker.setServiceInterface(APIKeyService.class);
    invoker.afterPropertiesSet();
    return (APIKeyService) invoker.getObject();
  }
}

In my Spock integration test I need to mock these beans, which I tried according to the Micronaut docs: https://docs.micronaut.io/latest/guide/index.html#replaces

This resulted in a test like this:

@MicronautTest
class StackoverflowSpecification extends Specification {

  @Inject
  AuthorizedClient authorizedClient

  @Inject
  UnauthorizedClient unauthorizedClient

  @Inject
  OfferLeadService offerLeadService

  @Inject
  APIKeyService apiKeyService

  @Factory
  @Replaces(factory = RemotingConfig.class)
  static class RemotingConfigTest extends Specification {

    @Singleton
    OfferLeadService offerLeadService() {
      return Mock(OfferLeadService)
    }

    @Singleton
    APIKeyService apiKeyService() {
      return Mock(APIKeyService)
    }
  }

  void "authenticated sessions request returns 200 ok"() {

    when:
    HttpResponse response = authorizedClient.getSession("AA-BB-CC")

    then:
    response.status == OK

    and: 'setup mock calls'
    1 * apiKeyService.find(_, _) >> buildApiKeyVO()
    1 * offerLeadService.containsHipHavingPostalCode(_, _) >> true
    0 * _
  }

  void "authenticated sessions request with wrong passphrase returns 403 forbidden"() {

    when:
    unauthorizedClient.getSessionWithWrongPassphrase("AA-BB-CC")

    then:
    HttpClientResponseException ex = thrown(HttpClientResponseException)

    then:
    ex.status == FORBIDDEN

    and: 'setup mock calls'
    1 * apiKeyService.find(_, _) >> buildApiKeyVO()
    1 * offerLeadService.containsHipHavingPostalCode(_, _) >> false
    0 * _
  }

  private static APIKeyVO buildApiKeyVO() {
    APIKeyVO key = new APIKeyVO()
    key.setId(1L)
    key.setValue("123")
    key.setEnabled(true)
    key.setRoles(List.of("ROLE_STANDARD"))
    key.setValidUntil(Instant.now().plus(100, ChronoUnit.DAYS))
    key.setDescription("CBC App")
    key.setAccountId("CBC")
    return key
  }
}

This solution is not working well. The two tests pass if they run in isolation, running both of them however, causes the second test to fail (the order is relevant here, so if the second test were to be on top, it would be the one passing).

When running both tests and debugging, I see that once the two mocks have been invoked as expected in the first test, all subsequent calls to the mocks result in null and false respectively despite specifying something else.

How do I go about mocking the two beans specified via RemotingConfig in the integration test?

James Kleeh :

You aren't using the @Replaces annotation correctly. The factory member is not meant to be used by itself, but to rather further qualify the type being replaced.

  @Factory
  static class RemotingConfigTest extends Specification {

    @Singleton
    @Replaces(bean = OfferLeadService.class, factory = RemotingConfig.class)
    OfferLeadService offerLeadService() {
      return Mock(OfferLeadService)
    }

    @Singleton
    @Replaces(bean = APIKeyService.class, factory = RemotingConfig.class)
    APIKeyService apiKeyService() {
      return Mock(APIKeyService)
    }
  }

Edit: The above still applies, however you're expecting your mocks to be reset between test executions. That won't happen with the above code. You need to use the @MockBean annotation which is part of micronaut-test.

@MockBean(OfferLeadService.class)
OfferLeadService offerLeadService() {
    return Mock(OfferLeadService)
}

@MockBean(APIKeyService.class)
APIKeyService apiKeyService() {
    return Mock(APIKeyService)
}

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=94393&siteId=1