16

我正在使用 spring cloud 的 eureka 和 feign 在某些服务之间进行通信(比如说 A 和 B)。现在我想对我的单个服务(A)的服务层进行单元测试。问题是,这个服务 (A) 正在使用一个 feign 客户端来请求另一个服务 (B) 的一些信息。

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

我的问题是:有没有办法模拟 feign 客户端,所以我可以在不运行 eureka 实例和服务(B)的情况下对我的服务(A)进行单元测试?

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

//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; 
    } 
}
4

3 回答 3

8

问题是......你甚至需要嘲笑吗?我经常看到人们提到“模拟”作为任何“不应该成为单元测试的一部分”的第一个解决方案。Mocking 是一种技术,而不是解决所有问题的方法。(见这里)。

如果您仍处于代码的早期阶段,只需重构并使用其他东西,而不是依赖于 Feign Client 的具体实例。你可以使用接口、抽象类、特征或任何你想要的东西。不要依赖对象本身,否则你必须“模拟它”。

public interface IWebClient {
  public String get(...);
  public String post(...);
} 

对于这个问题:但是我将拥有其他完全相同的代码(除了它将在 Feign 的具体实例上),那我该怎么办?好吧,您可以编写功能测试并调用可以在本地设置的 Web 服务器实例 - 或使用 Wiremock,正如 Marcin Grzejszczak 在其中一个答案中提到的那样。

public class FeignClientWrapper implements IWebClient {
  private feign = something

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

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

单元测试用于测试算法、if/else、循环:单元如何工作。不要编写代码来使模拟适合 - 它必须相反:您的代码应该具有较少的依赖关系,并且仅在需要验证行为时才应该模拟(否则您可以使用存根或假对象):你需要验证行为吗?您是否需要测试代码中是否调用了特定方法?或者一个特定的方法被 X、Y 和 Z 连续调用了 3 次?那么,是的,嘲笑是可以的。

否则,使用假对象:你想要的只是测试调用/响应,也许是状态码。您可能想要的只是测试您的代码如何响应不同的输出(例如,字段“错误”是否存在于 JSON 响应中)、不同的状态代码(假设客户端文档是正确的:GET 时为 200 OK,201当 POST 等)。

于 2015-12-21T14:24:28.490 回答
7

模拟 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();
    }
}
于 2017-05-01T18:46:03.800 回答
2

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

于 2016-01-08T10:50:12.700 回答