badgerface
badgerface

Reputation: 61

How to mock feign.Client.Default with Mockito

I'm writing a Dropwizard application and using Feign for building client calls to outside services. I have custom encoders and decoders I'm registering with the feign.Builder like so:

    this.feignBuilder = Feign.builder()
            .contract(new JAXRSContract()) // we want JAX-RS annotations
            .encoder(new JacksonEncoder()) // same as what dropwizard is using
            .decoder(new CustomDecoder())
            .errorDecoder(new CustomErrorDecoder())
            .requestInterceptor(new AuthKeyInterceptor(config.getInterceptor()));

I'm writing unit tests for the feign client calls so I can watch how the feign machinery deals with my encoder/decoder overrides and bubbles on exceptions. I'm not interested in writing integration tests with a fake server right now (this is the most common type of test i see people writing for this situation).

This should be straight forward. I want to mock the point at which feign makes the request and have it return my fake response. That means I should mock the call to feign.Client.Default.execute so it returns my fake response when it makes the request a this call site. An example of what that mock looks like:

String responseMessage = "{\"error\":\"bad\",\"desc\":\"blah\"}";
feign.Response feignResponse = FeignFakeResponseHelper.createFakeResponse(404,"Bad Request",responseMessage);
Client.Default mockFeignClient = mock(Client.Default.class);
try {
     when(mockFeignClient.execute(any(feign.Request.class),any(Request.Options.class))).thenReturn(feignResponse);
} catch (IOException e) {
     assertThat(true).isFalse(); // fail nicely
}

No luck. The Cleint.Default class isn't mocked when I reach the call site for the request in the code. What am I doing wrong?

Upvotes: 4

Views: 27027

Answers (2)

Yoaz Menda
Yoaz Menda

Reputation: 1706

As mentioned before, Mockito is not powerful enough. I solved this with a manual mock.

It's easier than it sounds:

MyService.Java

public class MyService{
    //My service stuff      

    private MyFeignClient myFeignClient;

    @Inject //this will work only with constructor injection
    public MyService(MyFeignClient myFeignClient){
        this.MyFeignClient = myFeignClient
    }


    public void myMethod(){
        myFeignClient.remoteMethod(); // We want to mock this method
    }
}

MyFeignClient.Java

@FeignClient("target-service")
public interface MyFeignClient{

    @RequestMapping(value = "/test" method = RequestMethod.GET)
    public void remotemethod();
}

If you want to test the code above while mocking the feignclient, do this:

MyFeignClientMock.java

@Component
public class MyFeignClientMock implements MyFeignClient {

    public void remoteMethod(){
         System.out.println("Mocked remoteMethod() succesfuly");
    }
}

MyServiceTest.java

@RunWith(SpringJUnit4ClassRunner.class)
public class MyServiceTest {

    private MyService myService;

    @Inject
    private MyFeignClientMock myFeignClientMock;

    @Before
    public void setUp(){
       this.myService = new MyService(myFeignClientMock); //inject the mock
    }

    //Do tests normally here...
}

Upvotes: 3

badgerface
badgerface

Reputation: 61

It turns out Mockito is not powerful enough to do the thing I assumed it could do. The correct solution is to use PowerMockito to mock the constructor so Client.Default returns the mocked instance when it is instantiated in the class that holds that reference.

After a lot of compilation-error pain I got PowerMockito to compile and it seemed like it was going to work. Alas it failed to return my mock and the calls were still going through. I've tried PowerMockito in the past and never got around to using it because of the extra problems it caused. So I'm still of the opinion that it's not super easy to just plug and play.

It's a shame that trying to do something like this is so hard.

Upvotes: 2

Related Questions