Как можно протестировать контроллер который обращается через Feign к 3 внешним сервисам?

Хотелось бы протестировать мой контролер на статус и возвращаемое значение. В ходе тестирования получаю ошибку. Как можно исправить данную ошибку или посоветуйте более лучшие способы решения проблемы.

Тест:

@RunWith(SpringRunner.class)
@WebMvcTest(CurrencyComporatorController.class)
@AutoConfigureMockMvc
public class CurrencyComporatorControllerTest {

@InjectMocks
private CurrencyComporatorController currencyComporatorController;
@Mock
private CurrencyComporatorServiceProxy currencyComporatorServiceProxy;
@Mock
private GifServiceProxy gifServiceProxy;
@Mock
private GiphyServiceProxy giphyServiceProxy;
@Autowired
private MockMvc mockMvc;

@BeforeEach
public void setup() {

    mockMvc = MockMvcBuilders.standaloneSetup(currencyComporatorController).build();

}

@Test
    public void testReturn200AndContentType() throws Exception {
        String url = "/compare-currency-excgange/charcode/{USD}";
        this.mockMvc.perform(get("/compare-currency-excgange/charcode/{charcode}","USD"))
                .andExpect(content().contentType(MediaType.IMAGE_GIF));
    }

}

Причина ошибки:

Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'currencyComporatorController': Unsatisfied dependency expressed through field 'currencyProxy'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'com.CurrencyComporator.service.CurrencyComporatorServiceProxy': Unexpected exception during bean creation; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.cloud.openfeign.FeignContext' available
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.resolveFieldValue(AutowiredAnnotationBeanPostProcessor.java:660)
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:640)
    at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:119)
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessProperties(AutowiredAnnotationBeanPostProcessor.java:399)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1413)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:601)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:524)
    at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:944)
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:917)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:582)
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:767)
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:759)
    at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:426)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:326)
    at org.springframework.boot.test.context.SpringBootContextLoader.loadContext(SpringBootContextLoader.java:123)
    at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:99)
    at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:124)
    ... 89 more
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'com.CurrencyComporator.service.CurrencyComporatorServiceProxy': Unexpected exception during bean creation; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.cloud.openfeign.FeignContext' available
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:537)
    at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208)
    at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:276)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1380)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1300)
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.resolveFieldValue(AutowiredAnnotationBeanPostProcessor.java:657)
    ... 109 more
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.cloud.openfeign.FeignContext' available
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:351)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:342)
    at org.springframework.cloud.openfeign.FeignClientFactoryBean.getTarget(FeignClientFactoryBean.java:331)
    at org.springframework.cloud.openfeign.FeignClientFactoryBean.getObject(FeignClientFactoryBean.java:322)
    at org.springframework.cloud.openfeign.FeignClientsRegistrar.lambda$registerFeignClient$0(FeignClientsRegistrar.java:224)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.obtainFromSupplier(AbstractAutowireCapableBeanFactory.java:1231)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1173)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:564)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:524)
    ... 117 more

Код контроллера:

@RestController
public class CurrencyComporatorController {
@Autowired
private CurrencyComporatorServiceProxy currencyProxy;
@Autowired
private GiphyServiceProxy giphyProxy;
@Autowired
private GifServiceProxy gifProxy;


@GetMapping(value = "/compare-currency-excgange/charcode/{name}")
public ResponseEntity<InputStreamResource> getGifByCurrencyChanges(@PathVariable String name) throws IOException {
    String query = getQueryGifPath(name);
    if (query.equals("")) {return  null;}
    Resource resource = gifProxy.retrieveGifImage(getGifIdFromUrl(url));
    return ResponseEntity.ok()
            .contentType(MediaType.IMAGE_GIF)
            .body(new InputStreamResource(resource.getInputStream()));
}

Ответы (1 шт):

Автор решения: Roman Konoval

Ключевая проблема в том, что WebMvcTest не создает всех бинов, а только контроллер. Про моки созданные в тесте с помощью @Mock контекст, который поднимается в тесте ничего не знает, нужно пользоваться @MockBean.

Правильный способ конфигурации mvc тестов такой:

@WebMvcTest(CurrencyComporatorController.class)
public class CurrencyComporatorControllerTest {

  @Autowired
  private MockMvc mvc;
  @MockBean
  private CurrencyComporatorServiceProxy currencyComporatorServiceProxy;
  @MockBean
  private GifServiceProxy gifServiceProxy;
  @MockBean
  private GiphyServiceProxy giphyServiceProxy;

  @Test
  public void test() {
  ...
  }
}

То есть:

  1. Использовать автоматически сконфигурированный MockMvc
  2. Вместо Mock использовать MockBean (у вас сейчас ошибка из-за этого).
  3. Если в тесте вдруг нужен доступ к самому контроллеру, то можно его через @Autowire добавить, но это в таких тестах делать неправильно. Я так понимаю, что сейчас у вас он есть, как попытка внедрить зависимости.
  4. Не нужно использовать 'SpringRunner.class'
→ Ссылка