如何使用不同的 id 两次模拟 webclient uri(相同的 uri 和不同的变量)

发布于 2025-01-12 11:35:09 字数 2667 浏览 3 评论 0原文

我有这个 webClient 模拟,如果我只模拟单个 ID,它就可以工作。但在这里我将有一个程序调用两个不同的帐户并执行一些操作,因此我需要像下面的代码一样调用 webClient 两次,如果此代码有效,我将能够编写主要测试。

@AutoConfigureWebTestClient
@ExtendWith(MockitoExtension.class)
@AutoConfigureWebFlux
@SpringBootTest(classes = Application.class)
class AccountServiceTest {

    @Autowired
    private AccountServiceInterface accountService;;
    @Mock
    WebClient webClientMock;
    @Mock
    WebClient.RequestHeadersUriSpec requestHeadersUriSpecMock;
    @Mock
    WebClient.RequestHeadersSpec requestHeadersSpecMock;
    @Mock
    WebClient.ResponseSpec responseSpecMock;

    @BeforeEach
    void setUp() {
        ReflectionTestUtils.setField(accountService, "webClient", webClientMock);
    }

    private void mockAccount(Long accountId){
        AccountGradeDto mockAccount = new AccountGradeDto(accountId, 1L, 1L, new BigDecimal(1000), AccountStatus.ACTIVE, 10L);

        when(webClientMock.get()).thenReturn(requestHeadersUriMock);
        when(requestHeadersUriMock.uri("/api/account/management/v1/get-account-grade/{id}", accountId)).thenReturn(requestHeadersSpecMock);
        when(requestHeadersSpecMock.retrieve()).thenReturn(responseSpecMock);
        when(responseSpecMock.onStatus(any(), any())).thenReturn(responseSpecMock);
        when(responseSpecMock.bodyToMono(AccountGradeDto.class)).thenReturn(Mono.just(mockAccount));
    }

    @Test
    public void testAccountService() {
        mockAccount(1L);
        mockAccount(2L);

        StepVerifier.create(accountService.getAccountById(2L))
                .expectNextMatches(account -> account.getId().equals(2L))
                .verifyComplete();

        StepVerifier.create(accountService.getAccountById(1L))
                .expectNextMatches(account -> account.getId().equals(1L)) //TEST FAILS HERE AS IT RETURNS SAME OBJECT
                .verifyComplete();
}

这是

@Service
public class AccountServiceImpl implements AccountServiceInterface {

    private final WebClient webClient;

    public AccountServiceImpl(@Value("${app.resource.account.service-base-url}") String baseUrl) {
        this.webClient = WebClient.create("http://" + baseUrl);
    }

    @Override
    public Mono<AccountGradeDto> getAccountById(Long accountId) {
        return webClient.get()
            .uri("/api/account/management/v1/get-account-grade/{id}", accountId)
            .retrieve()
            .onStatus(HttpStatus.NOT_FOUND::equals, clientResponse -> Mono.empty())
            .bodyToMono(AccountGradeDto.class).transform(ReactiveUtils::errorIfEmpty);
    }
}

当我运行测试时如果在第二次匹配(id:1L)失败时的服务实现,因为它返回为 ID 2 模拟的相同对象

I have this webClient mock and it works if I only mock single ID. but here I will have a procedure that calls two different accounts and do some stuffs so I need to call a webClient twice like the blow code if this code works I'll be able to write the main test.

@AutoConfigureWebTestClient
@ExtendWith(MockitoExtension.class)
@AutoConfigureWebFlux
@SpringBootTest(classes = Application.class)
class AccountServiceTest {

    @Autowired
    private AccountServiceInterface accountService;;
    @Mock
    WebClient webClientMock;
    @Mock
    WebClient.RequestHeadersUriSpec requestHeadersUriSpecMock;
    @Mock
    WebClient.RequestHeadersSpec requestHeadersSpecMock;
    @Mock
    WebClient.ResponseSpec responseSpecMock;

    @BeforeEach
    void setUp() {
        ReflectionTestUtils.setField(accountService, "webClient", webClientMock);
    }

    private void mockAccount(Long accountId){
        AccountGradeDto mockAccount = new AccountGradeDto(accountId, 1L, 1L, new BigDecimal(1000), AccountStatus.ACTIVE, 10L);

        when(webClientMock.get()).thenReturn(requestHeadersUriMock);
        when(requestHeadersUriMock.uri("/api/account/management/v1/get-account-grade/{id}", accountId)).thenReturn(requestHeadersSpecMock);
        when(requestHeadersSpecMock.retrieve()).thenReturn(responseSpecMock);
        when(responseSpecMock.onStatus(any(), any())).thenReturn(responseSpecMock);
        when(responseSpecMock.bodyToMono(AccountGradeDto.class)).thenReturn(Mono.just(mockAccount));
    }

    @Test
    public void testAccountService() {
        mockAccount(1L);
        mockAccount(2L);

        StepVerifier.create(accountService.getAccountById(2L))
                .expectNextMatches(account -> account.getId().equals(2L))
                .verifyComplete();

        StepVerifier.create(accountService.getAccountById(1L))
                .expectNextMatches(account -> account.getId().equals(1L)) //TEST FAILS HERE AS IT RETURNS SAME OBJECT
                .verifyComplete();
}

and here is the service implementation

@Service
public class AccountServiceImpl implements AccountServiceInterface {

    private final WebClient webClient;

    public AccountServiceImpl(@Value("${app.resource.account.service-base-url}") String baseUrl) {
        this.webClient = WebClient.create("http://" + baseUrl);
    }

    @Override
    public Mono<AccountGradeDto> getAccountById(Long accountId) {
        return webClient.get()
            .uri("/api/account/management/v1/get-account-grade/{id}", accountId)
            .retrieve()
            .onStatus(HttpStatus.NOT_FOUND::equals, clientResponse -> Mono.empty())
            .bodyToMono(AccountGradeDto.class).transform(ReactiveUtils::errorIfEmpty);
    }
}

when I run the test if fails at second match (id: 1L) as it returns the same objects mocked for ID 2

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

海拔太高太耀眼 2025-01-19 11:35:09

我们不应该使用单例范围来模拟 WebClient.RequestHeadersSpecWebClient.ResponseSpec 并且需要对同一 uri 上的每个请求进行模拟。

private void mockAccount(Long accountId){
    AccountGradeDto mockAccount = new AccountGradeDto(accountId, 1L, 1L, new BigDecimal(1000), AccountStatus.ACTIVE, 10L);

    WebClient.ResponseSpec responseSpecMock = mock(WebClient.ResponseSpec.class);
    WebClient.RequestHeadersSpec requestHeadersSpecMock = mock(WebClient.RequestHeadersSpec.class);

    when(webClientMock.get()).thenReturn(requestHeadersUriMock);
    when(requestHeadersUriMock.uri("/api/account/management/v1/get-account-grade/{id}", accountId)).thenReturn(requestHeadersSpecMock);
    when(requestHeadersSpecMock.retrieve()).thenReturn(responseSpecMock);
    when(responseSpecMock.onStatus(any(), any())).thenReturn(responseSpecMock);
    when(responseSpecMock.bodyToMono(AccountGradeDto.class)).thenReturn(Mono.just(mockAccount));
}

we should not use singleton scope for mocking WebClient.RequestHeadersSpec and WebClient.ResponseSpec and need to be mocked for every request on same uri.

private void mockAccount(Long accountId){
    AccountGradeDto mockAccount = new AccountGradeDto(accountId, 1L, 1L, new BigDecimal(1000), AccountStatus.ACTIVE, 10L);

    WebClient.ResponseSpec responseSpecMock = mock(WebClient.ResponseSpec.class);
    WebClient.RequestHeadersSpec requestHeadersSpecMock = mock(WebClient.RequestHeadersSpec.class);

    when(webClientMock.get()).thenReturn(requestHeadersUriMock);
    when(requestHeadersUriMock.uri("/api/account/management/v1/get-account-grade/{id}", accountId)).thenReturn(requestHeadersSpecMock);
    when(requestHeadersSpecMock.retrieve()).thenReturn(responseSpecMock);
    when(responseSpecMock.onStatus(any(), any())).thenReturn(responseSpecMock);
    when(responseSpecMock.bodyToMono(AccountGradeDto.class)).thenReturn(Mono.just(mockAccount));
}
南汐寒笙箫 2025-01-19 11:35:09

模拟 WebClient 可能非常棘手且容易出错。即使您可以涵盖一些简单的积极案例,也很难测试更复杂的场景,例如错误处理、重试……

我建议 WireMock 它为测试 Web 客户端提供了一个非常好的 API。以下是一些示例

stubFor(get("/api/account/management/v1/get-account-grade/1")
        .withHeader(HttpHeaders.ACCEPT, containing(MediaType.APPLICATION_JSON_VALUE))
        .willReturn(aResponse()
                .withStatus(200)
                .withBody(...)
        )
);

StepVerifier.create(service.getAccountById(1))
        .expectNext(...)
        .verifyComplete();

来轻松测试正面和负面场景

stubFor(get("/api/account/management/v1/get-account-grade/1")
        .withHeader(HttpHeaders.ACCEPT, containing(MediaType.APPLICATION_JSON_VALUE))
        .willReturn(aResponse()
                .withStatus(500)
        )
);

您可以通过提供不同的存根或使用 场景 测试重试逻辑 甚至使用 延迟 来模拟超时

Mocking WebClient could be very tricky and error-prone. Even if you could cover some simple positive cases, it would be very hard to test more complex scenarios, like error handling, retries, ...

I would recommend WireMock which provides a very good API for testing web clients. Here are some examples

stubFor(get("/api/account/management/v1/get-account-grade/1")
        .withHeader(HttpHeaders.ACCEPT, containing(MediaType.APPLICATION_JSON_VALUE))
        .willReturn(aResponse()
                .withStatus(200)
                .withBody(...)
        )
);

StepVerifier.create(service.getAccountById(1))
        .expectNext(...)
        .verifyComplete();

You could easily test both positive and negative scenarios by providing different stubs

stubFor(get("/api/account/management/v1/get-account-grade/1")
        .withHeader(HttpHeaders.ACCEPT, containing(MediaType.APPLICATION_JSON_VALUE))
        .willReturn(aResponse()
                .withStatus(500)
        )
);

or test retry logic using Scenarios or even simulate timeouts using Delays

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文