模拟 OpenFeign 客户端以在 Spring 库中进行单元测试,而不是在 Spring Boot 应用程序中进行单元测试

Den*_*din 5 java spring microservices spring-cloud-feign openfeign

我已经实现了一个基于此官方存储库调用 get API 的 feign 客户端。我有一个规则类UserValidationRule,需要调用它来获取 API 调用getUser()并验证一些内容。这按预期工作,但是当我开始测试该规则类时,模拟假客户端并不成功,它会继续调用实际的 API。我已经简化了情况,所以请忽略简单性哈哈。这是我发现这个stackoverflow问题后的一个后续问题

API 返回此模型:

@Data
public class userModel {
    private long id;
    private String name;
    private int age;

}
Run Code Online (Sandbox Code Playgroud)

与其余客户端方法的接口:

public interface UserServiceClient {

    @RequestLine("GET /users/{id}")
    UserModel getUser(@Param("id") int id);
}
Run Code Online (Sandbox Code Playgroud)

在规则类中,我构建了 feign 客户端并调用 API:

@RequiredArgsConstructor
@Component
public class UserValidationRule {

    private static final String API_PATH = "http://localhost:8080";

    private UserServiceClient userServiceClient;


    public void validate(String userId, ...) {
            // some validations 
            validateUser(userId);

    }

    private void validateUser(String userId) {
            userServiceClient = getServiceClient();
            UserModel userModel = userServiceClient.gerUser(userId);
            
            // validate the user logic
        }
    }

    private UserServiceClient getServiceClient() {
           return Feign.builder()
                  .encoder(new GsonEncoder())
                  .decoder(new GsonDecoder())
                  .target(UserServiceClient.class, API_PATH);
    }
}
Run Code Online (Sandbox Code Playgroud)

测试类来了:

public class UserValidationRuleTest {

    private UserServiceClient userServiceClient = mock(UserServiceClient.class);
    private UserValidationRule validationRule = new UserValidationRule();
    private UserModel userModel;

    @Before
    public void init() {
        userModel = generateUserModel();
    }


    @Test
    public void validateWhenAgeIsNotBlank() {

        doReturn(userModel).when(userServiceClient).getUser(any());
        validationRule.validate("123", ...);
        // some logic ...
        assertEquals(.....);
        verify(userServiceClient).getUser(any());
    }



    private UserModel generateUserModel() {
        UserModel userModel = new UserModel();
        userModel.setName("Cody");
        userModel.setAge("22");
        return accountModel;
    }
}
Run Code Online (Sandbox Code Playgroud)

当我调试时validateWhenAgeIsNotBlank(),我发现 userModel 不是在测试类中生成的,并且值都是 null。如果我传入一个实际的userId,我会得到一个数据库中的实际 UserModel 。

我认为问题在于UserServiceClient没有被嘲笑。失败verify,因为它说getUser()未调用。这可能与 feign.builder() 中如何声明 feign 客户端有关UserValidationRule...如果我错了,请纠正我,并告诉我我缺少什么或有关如何正确模拟它的任何建议。

Set*_*etu 4

您没有使用 spring 管理的UserServiceClientbean。每次调用UserValidationRule.validate它时validateUser,都会调用该getServiceClient方法。这会为每次调用getServiceClient创建一个新实例UserServiceClient。这意味着在测试模拟时UserServiceClient根本没有被使用。

我将重构代码如下;

首先使用 声明UserServiceClient为最终版本@RequiredArgsConstructor或替换@RequiredArgsConstructor@AllArgsConstructor。此更改的目的是允许UserServiceClient注入实例,而不是在服务方法内部创建实例。

@Component
@RequiredArgsConstructor
public class UserValidationRule {

    private final UserServiceClient userServiceClient;

.... // service methods

}
Run Code Online (Sandbox Code Playgroud)

然后有一个单独的Configuration类,将 feign 客户端构建为 spring bean;

@Bean
private UserServiceClient userServiceClient() {
       return Feign.builder()
              .encoder(new GsonEncoder())
              .decoder(new GsonDecoder())
              .target(UserServiceClient.class, API_PATH);
}
Run Code Online (Sandbox Code Playgroud)

在运行时,该 bean 将被注入到UserValidationRule.

至于单元测试更改,您正在正确创建模拟,但没有在任何地方设置/注入该模拟。您需要使用@Mock注释@InjectMocks或手动创建UserValidationRule实例@Before

这是如何@Mock@InjectMocks使用应该是什么样子的;

@RunWith(MockitoJUnitRunner.class)
public class UserValidationRuleTest {

    @Mock private UserServiceClient userServiceClient;
    @InjectMocks private UserValidationRule validationRule;
    ... rest of the code
Run Code Online (Sandbox Code Playgroud)

或继续使用mock(...)方法并手动创建UserValidationRule.

public class UserValidationRuleTest {

    private UserServiceClient userServiceClient = mock(UserServiceClient.class);
    private UserValidationRule validationRule;
    private UserModel userModel;

    @Before
    public void init() {
        validationRule = new UserValidationRule(userServiceClient);
        userModel = generateUserModel();
    }
    ... rest of the code
Run Code Online (Sandbox Code Playgroud)

现在,这将确保您在运行时使用 spring 管理的 feign 客户端 bean 的单个实例,并使用模拟实例进行测试。