Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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());
  }
}
like image 508
Juan Vega Avatar asked May 30 '17 15:05

Juan Vega


2 Answers

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;
    }
}
like image 87
yongsung.yoon Avatar answered Oct 13 '22 21:10

yongsung.yoon


The annotation @EnableFeignClients will create primary beans for interfaces marked with @FeignClient by default. Disabling this for every interface may have side effects, like when there are fallback beans.

In order to prevent the creation of the Feign beans in the first place, just move this annotation to a configuration class which is disabled for tests.

@Configuration
@Profile("!test")
@EnableFeignClients
public class FeignEnable {

}

Then annotation your test class with @ActiveProfiles("test").

like image 38
Stephan Windmüller Avatar answered Oct 13 '22 20:10

Stephan Windmüller