为单元测试模拟Eureka Feign客户端

17

我正在使用Spring Cloud的Eureka和Feign来在一些服务(比方说A和B)之间进行通信。现在,我想对单个服务(A)的服务层进行单元测试。问题是,该服务(A)使用Feign客户端请求其他服务(B)的某些信息。

在没有任何特殊配置的情况下运行unittests会抛出以下异常:java.lang.RuntimeException: com.netflix.client.ClientException: Load balancer does not have available server for client: service-b => 但我不想运行任何服务器。

我的问题是:是否有一种方法可以模拟Feign客户端,以便我可以在不运行Eureka实例和服务(B)的情况下对服务(A)进行单元测试?

编辑: 最终,我创建了一个Feign客户端存根。该存根被标记为主要组件,以强制Spring在我的测试中实例化该存根。
这就是我想出的解决办法。

//the feign client
@FeignClient("user") 
public interface UserClient { 
    UserEntity getUser(); 
}

//the implementation i use for the tests 
@Component 
@Primary //mark as primary implementation
public class UserClientTestImpl implements UserClient { 
    @Override public UserEntity getUser() { 
        return someKindOfUser; 
    } 
}
3个回答

9
问题是...你是否需要模拟?我经常看到人们提到"模拟"作为任何"不应该成为单元测试的一部分"的解决方案。模拟是一种技术,而不是万能的解决方案(见这里)。
如果你仍处于代码的早期阶段,只需重构并使用其他东西来代替依赖于Feign客户端的具体实例。你可以使用接口、抽象类、特质或任何你想要的东西。不要依赖于对象本身,否则你就必须"模拟它"。
public interface IWebClient {
  public String get(...);
  public String post(...);
} 

对于这个问题,如果我有其他代码将做完全相同的操作(除了它将在 Feign 的具体实例上运行),该怎么办呢? 那么,您可以编写一个功能测试,并调用一个您可以在本地设置的 Web 服务器的实例 - 或者使用 Marcin Grzejszczak 在其中一个答案中提到的 Wiremock。
public class FeignClientWrapper implements IWebClient {
  private feign = something

  public String get() {
    feign.get( ... ) 
  }

  public String post() {
    feign.post( ... ) 
  }
} 

单元测试用于测试算法、if/else、循环等“单元”的工作方式。不要编写代码以适应模拟 - 它必须反过来:您的代码应具有更少的依赖项,并且只有在需要验证行为时才应进行模拟(否则可以使用存根或虚拟对象):您需要验证行为吗?您需要测试特定方法是否在您的代码中被调用吗?或者,特定方法连续3次被调用时是否带有X、Y和Z?如果是,那么模拟就可以。

否则,请使用虚拟对象:您想要的是仅测试调用/响应以及可能的状态码。您可能只想测试代码对不同输出的反应方式(例如,在JSON响应中,“error”字段是否存在),不同的状态码(假设客户端文档是正确的:当GET时返回200 OK,当POST时返回201等)。


这是我目前想出的解决方案:Feign接口 @FeignClient("user") public interface UserClient { //一些Feign注释 UserEntity getUser(); }我用于测试的实现 `@Component @Primary public class UserClientTestImpl implements UserClient {@Override public UserEntity getUser() { return someKindOfUser; }}`基本上就是你提到的方法@Markon。 - Kamil Szuster
1
你能否在问题中发布代码?在评论中阅读代码很困难:D我很高兴它有所帮助。当您想要测试行为时,可以使用模拟。如果您想要测试“连接”,请设置一个小型Web服务器!:P - Markon
我需要模拟FeignClient的响应,以便我不启动其他微服务。这有什么问题吗?此外,我不想为这样一个简单的情况使用外部附加库。 - Kirill Ch
@KirillCh,我们尝试一下,我会尽力回答的,什么时候有机会的话。;) - Markon
这里有一个问题 https://stackoverflow.com/questions/57328839/how-to-properly-emulate-feignclient-responses-with-junit 期待你的答案。 - Kirill Ch
显示剩余2条评论

9

在微服务组件测试中,模拟Feign客户端非常有用。您希望测试一个微服务而无需启动所有其他微服务。

如果您正在使用Spring(并且看起来是这样),那么@MockBean注释以及一些Mockito代码将完成工作。

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = 
SpringBootTest.WebEnvironment.DEFINED_PORT)
public class TestYourComponent {
    @Configuration
    @Import({YourConfiguration.class})
    public static class TestConfiguration {
    }

    @MockBean
    private UserClient userClient;

    @Test
    public void someTest()
    {
        //...
        mockSomeBehavior();
        //...
    }

    private void mockSomeBehavior() {
        Mockito.doReturn(someKindOfUser).when(userClient).getUser();
    }
}

通常,这不是单元测试应该做的事情。您可以通过查看测试的依赖项来真正了解此内容:SpringBootTest 应该在这里提醒您,因为您正在初始化整个 SpringBoot 上下文(+ 所有 bean 等)。在单元测试中,您需要什么?这更多关于集成/组件测试而不是单元测试。 - Markon
1
@Markon,如果你读了我写的内容,你会发现我在谈论组件测试,而不是单元测试。也许你想回答原始问题,因为它确实声称是关于单元测试的?尽管如此,很明显原始提问者即使使用了错误的词语,也是指组件测试。 - Myrle Krantz
是的,我知道这是关于组件测试的。我只是想在你的回答中添加一些内容,因为我看到越来越多的人将组件测试(就像你写的那个)称为单元测试,这让人感到困惑。而我也知道你知道它们之间的区别 :) - Markon

4
如果您需要使用模拟数据,可以使用Wiremock来为给定的请求存根响应-http://wiremock.org/stubbing.html。这样,您将使用实际发送的HTTP请求进行集成测试。对于单元测试,@Markon的答案非常好。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接