模拟 Eureka Feign 客户端以进行单元测试

Mock an Eureka Feign Client for Unittesting

我正在使用 spring 云的 eureka 并假装在某些服务(假设 A 和 B)之间进行通信。现在我想对单个服务 (A) 的服务层进行单元测试。问题是,此服务 (A) 使用假客户端请求其他服务 (B) 的一些信息。

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

我的问题是:有没有办法模拟假客户端,这样我就可以在没有 运行 一个尤里卡实例和服务 (B) 的情况下对我的服务 (A) 进行单元测试?

编辑: 我最终为假装客户创建了一个存根。存根被标记为主要组件,以强制 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; 
    } 
}

问题是……你还需要模拟吗?我经常看到人们提到 "mock" 作为任何 "should not be part of the unit test" 的第一个解决方案。模拟是一种技术,而不是解决所有问题的方法。 (参见 here)。

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

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,循环:units 是如何工作的。不要编写代码来使模拟适合——它必须是相反的:你的代码应该有更少的依赖性,你应该只在你需要验证行为时模拟(否则你可以使用存根或假对象):您需要验证行为吗?您是否需要测试代码中是否调用了特定方法?或者连续 3 次使用 X、Y 和 Z 调用特定方法?好吧,那是的,嘲笑是可以的。

否则,使用假对象:您想要的只是测试 call/response 和状态代码。您可能想要的只是测试您的代码如何对不同的输出做出反应(例如,字段 "error" 是否存在于 JSON 响应中)、不同的状态代码(假设客户端文档是正确的: GET 时 200 OK,POST 时 201,等等)。

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

模拟假客户端在微服务组件测试中非常有用。您希望在不启动所有其他微服务的情况下测试一个微服务。

如果您正在使用 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();
    }
}