Search code examples
spockmicronaut

Micronaut @Replaces with declarative Client


I am going to use the code from Micronaut Documentation (Declarative Http Client)- And I'm using Spock

PetOperations.java

@Validated
public interface PetOperations {
    @Post
    Single<Pet> save(@NotBlank String name, @Min(1L) int age);
}

I have a declarative client:

@Client("/pets") 
public interface PetClient extends PetOperations { 

    @Override
    Single<Pet> save(String name, int age); 
}

My goal is when I run a test class, I want to call (@Replaces) another class (PetDummy) instead of the PetClient, PetDummy class is located in my test folder

@Primary
@Replaces(PetClient.class)
@Singleton
public class PetDummy implements PetOperations {

    @Override
    public Single<Pet> save(String name, int age) {
        Pet pet = new Pet();
        pet.setName(name);
        pet.setAge(age);
        // save to database or something
        return Single.just(pet);
    }
}

test class:

class PetTest extends Specification {

    @Shared
    @AutoCleanup
    ApplicationContext applicationContext = ApplicationContext.run();
    //EmbeddedServer server = applicationContext.getBean(EmbeddedServer.class).start();

    PetClient client = applicationContext.getBean(PetOperations.class);


    def 'test' (){
        given: 'name and age'

        when:
        client.save("Hoppie", 1);

        then:
        noExceptionThrown()
    }
}

However, at the end PetClient is called, I have as well tried with the @Factory annotation, but no success

PetClient extends PetOperations and PetDummy implements PetOperations, if they both implement then it will make sense to use @Replaces ...

Is there something else I can try out?

Thank you!

Another Issue:

Now that it works, the PetClient is a dependency in my PetService. When I test my PetService, it still calls the PetClient instead of the PetDummy.

I assume it has to do with the applicationContext, you will see

PetService:

PetService {
    @Inject
    PetClient client;

    buyFood(){
        //...
        Single<Pet> pet = client.save("Hoppie", 1));
    }
}

PerService Test:

class PetServiceTest extends ApplicationContextSpecification {

    @Subject
    @Shared
    PetService petService = applicationContext.getBean(PetService)

    PetOperations client = applicationContext.getBean(PetOperations.class) //client is not used here

    def 'test' (){
        given:

        when:
        petService.buyFood()

        then:
        noExceptionThrown()
    }
}

I think that I need to "get into" the applicationContext from the PetService, to tell "use the PetDummy" implementation (Inside the test class, because the ApplicationContextSpecification belong to another module

The ApplicationContextSpecification is:

abstract class ApplicationContextSpecification extends Specification implements ConfigurationFixture {

    @AutoCleanup
    @Shared
    ApplicationContext applicationContext = ApplicationContext.run(configuration)

/*    def cleanup() {
        assert !hasLeakage()
    }*/

}

The ConfigurationFixture contains the properties for the database(hibernate)


Solution

  • You are already retrieving the PetClient bean implementation:

    PetClient client = applicationContext.getBean(PetOperations.class);
    

    Which should provide the replacing dummy bean implementation if called with the appropriate type:

    PetOperations client = applicationContext.getBean(PetOperations.class);