Search code examples
javaspringunit-testingmockitofeign

Override @FeignClient using a @Configuration bean for tests


Is it possible to override a bean created through the @FeignClient annotation by just creating a @Configuration bean that contains a mocked version of it for testing?

I've already tried it but it seems the @FeignClient bean is created last (or so I think) as in my test I always get injected the real version instead of the mocked one. In the same config file I have another bean created without any annotations (apart from @Component) mocked the same way by just using the name of the real one and it works perfectly.

I've tried using @MockBean to mock it and it works but the project has some quirks on it that make the creation of another Spring context break the tests.

Thanks.

EDIT. Actually, I just debugged the test and realised that, if I use the same name as the Feign client, the debugger doesn't even stop in the @Configuration bean to create the mocked version. Changing the name to something else it works but it just creates another bean of the same type with the new name. Is there any configuration I'm missing here?

EDIT 2. This is an example code. Executing this I have that BarService is the mocked version but FooService is the real one.

@FeignClient(name = "fooService")
public interface FooService {
}

@Component
public class BarService {
}

@Configuration
public class ConfigClass {

  @Bean
  public FooService fooService() {
    return Mockito.mock(FooService.class);
  }

  @Bean
  public BarService barService() {
    return Mockito.mock(BarService.class);
  }

@RunWith(SpringRunner.class)
@ActiveProfiles("test")
@SpringBootTest
public class TestClass {

  @Autowired
  private FooService fooService;

  @Autowired
  private BarService barService;

  @Test
  public void test() {
    System.out.println(fooService.getClass());
  }
}

Solution

  • Your problem is caused by the fact that FeignClient beans are defined as primary like declaring a bean with @Primary. So it has a priority over other normal beans. From spring-cloud-netflix 1.3.0 that is included from Dalston release, you can turn off this primary configuration like below.

    @FeignClient(name = "fooService", primary = false)
    public interface FooService {
    }
    

    If you change your code like above, Mocked bean will be injected into your test.

    One thing you need to be careful is that primary option is used when you are using fallback bean for your FeignClient. If you have fallback bean, you might need to specify FeignClient bean with qualifer to get FeignClient bean over fallback bean.

    I think that another way to inject mocked bean instead of FeignClient bean for the test is using BeanPostProcessor something like below.

    public static class MockProcessor implements BeanPostProcessor {
             : 
             :
        @Override
        public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
            if (/* distinguish your target bean */) 
                return Mockito.mock(FooService.class);
            return bean;
        }
    }