6

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());
  }
}
Juan Vega
  • 1,030
  • 1
  • 16
  • 32
  • Looks like you need to be actually fixing those quirks. Why would loading a completely separate context break other tests ??? – M. Deinum May 30 '17 at 15:30
  • Basically the project uses Quartz and the test uses Hibernate with a H2 embedded database. When starting, it automatically creates some tables for Quartz through a script. When destroying the contexts, one of them throws an exception because the other one has closed the database already. Not a big deal but it leaves an ugly log and people are not happy about it. I don't know exactly how to fix it as it's the close of the contexts that triggers the destruction of the database and I don't know if I can configure the extra one to avoid it. I thought this approach would be faster – Juan Vega May 30 '17 at 16:06
  • Apart from that, I'm not familiar with the internals of `@MockBean`. My guess is that it only creates one extra context to hold the mock beans and that it doesn't keep creating new ones every time a test defines a mock that hasn't been created already. Is this the case? Because the context creation actually impacts in the time it takes to run the tests because of Hibernate been started up with the sql scripts that the app uses. – Juan Vega May 30 '17 at 16:10
  • It creates a new fresh context based on the mocks and obviously this will impact performance. You can use context hierarchies but not sure how that would work in a Spring Boot environment. But it will only create a new one for this test (the application contexts are cached and reused between tests). `@MockBean` creates a mock for the bean and integrates more tightly with the context and automatically resets mocks etc. – M. Deinum May 30 '17 at 17:52

4 Answers4

6

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;
    }
}
yongsung.yoon
  • 5,489
  • 28
  • 32
  • Interestingly enough, that works but only if I define the mocked version as a bean in the production code with the `@Primary` annotation to be recognised automatically by Spring. If I create a `@Configuration` class in my test folder and the mocked version inside it using `@Bean`, Spring doesn't even load it. Adding an extra `@Bean` of anything else gets loaded so it's not that the `@Configuration` class is overlooked. The option of the `BeanPostProcessor` also works. – Juan Vega Jun 01 '17 at 09:07
2

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").

  • The `@Profile` annotation is also available on method level so that also only specific Beans can be disabled/overwritten. – dnl.re Sep 07 '20 at 11:53
1

it would be great if you can share your test class.

As I understood your problem is that you need to override a bean within your test.

To do this you can refer to the following question:

Overriding an Autowired Bean in Unit Tests

nikita_pavlenko
  • 658
  • 3
  • 11
  • I just added an example of the problem. As I said, I can just use `@MockBean` in the test but that would lead to the extra contexts. And I'm also curious of why this happens. – Juan Vega May 30 '17 at 16:07
1

It is a bug and it was fixed in 2.2.3 Feign.

LW001
  • 2,452
  • 6
  • 27
  • 36
JD_UA
  • 43
  • 5