Reputation: 61
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
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
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