Flurl可测试的HTTP

2058 更新于: 2021-03-15 读完约需 4 分钟

Flurl.Http提供了一组测试特性,这些特性使得测试变得非常简单。Flurl.Http的核心是HttpTest,它创建HttpTest实例将Flurl带入测试模式,测试主题中的所有HTTP活动都会自动伪造并记录,比如:

using Flurl.Http.Testing;

[Test]
public void Test_Some_Http_Calling_Method() {
    using (var httpTest = new HttpTest()) {
        // Flurl现在进入了测试模式
        sut.CallThingThatUsesFlurlHttp(); // 模拟HTTP请求!
    }
}

大多数单元测试框架都有一些setup或者teardown方法的概念,这些方法在每个测试之前/之后执行。对于有很多针对http调用代码的测试的类,您可能更喜欢下面这种方法:

private HttpTest _httpTest;

[SetUp]
public void CreateHttpTest() {
    _httpTest = new HttpTest();
}

[TearDown]
public void DisposeHttpTest() {
    _httpTest.Dispose();
}

[Test]
public void Test_Some_Http_Calling_Method() {
    // Flurl处于测试模式
}

注意:由于SUT中用于信号调用伪造的机制存在已知问题,从异步设置方法实例化HttpTest将无法正常工作。

默认情况下,模拟的HTTP调用返回一个200(OK)状态和一个空的主体。当然,你可能希望根据其他响应测试你的代码。

httpTest.RespondWith("响应数据");
sut.DoThing();

使用json对象的响应:

httpTest.RespondWithJson(new { x = 1, y = 2 });

测试失败的条件:

httpTest.RespondWith("server error", 500);
httpTest.RespondWithJson(new { message = "unauthorized" }, 401);
httpTest.SimulateTimeout();

RespondWith*开头的方法都是链式的,如下:

httpTest
    .RespondWith("some response body")
    .RespondWithJson(someObject)
    .RespondWith("error!", 500);

sut.DoThingThatMakesSeveralHttpCalls();

在后台,每个RespondWith*都会向线程安全队列添加一个假响应。

从Flurl 3.0开始,你还可以设置仅适用于匹配特定条件的请求的响应。以下代码演示了所有可能的条件:

httpTest
    .ForCallsTo("*.api.com*", "*.test-api.com*") // 多个请求地址,支持通配符
    .WithVerb("put", "PATCH") // 或者 HttpMethod.Put, HttpMethod.Patch
    .WithQueryParam("x", "a*") // 参数值,支持通配符
    .WithQueryParams(new { y = 2, z = 3 })
    .WithAnyQueryParam("a", "b", "c")
    .WithoutQueryParam("d")
    .WithHeader("h1", "f*o") // 参数值,支持通配符
    .WithoutHeader("h2")
    .WithRequestBody("*something*") // 支持通配符
    .WithRequestJson(new { a = "*", b = "hi" }) // 字符串支持通配符
    .With(call => true) // 检查FlurlCall上的所有条件
    .Without(call => false) // 检查FlurlCall上的所有条件
    .RespondWith("所有条件都满足!", 200);

如果在某些情况下需要发起真正的请求?调用AllowRealHttp()方法即可,如下:

httpTest
    .ForCallsTo("https://api.thirdparty.com/*")
    .AllowRealHttp();

一旦HttpTest被创建且任何特定的响应都进入了队列,则只需要简单地调用一个测试主题即可。当SUT使用Flurl进行HTTP调用时,真正的调用会被有效地阻塞,而下一个模拟的响应会被离队并返回。

然而,当队列中只有一个响应(如果提供了匹配任何筛选条件)时,该响应就会变得“粘性”,也就是说,它没有退出队列,因此会在所有后续调用中返回。

不需要模拟或存根任何Flurl对象来实现这个功能。HttpTest使用逻辑异步调用上下文在SUT中传递信号,并通知Flurl模拟调用。

由于HTTP调用是伪造的,它们将自动记录到一个调用日志中,从而允许您断言某些调用是已经发出。断言与测试框架无关;当找不到指定的匹配项时,它们会在任何时候抛出异常,这实际上表明了所有测试框架中的测试失败。

HttpTest针对调用日志提供了两种断言方法:

sut.DoThing();

httpTest.ShouldHaveCalled("http://some-api.com/*");
httpTest.ShouldNotHaveCalled("http://other-api.com/*");

httpTest.ShouldHaveMadeACall();
httpTest.ShouldNotHaveMadeACalled();

当然,您可以对特定的调用进行进一步的断言:

httpTest.ShouldHaveCalled("http://some-api.com/*")
    .WithQueryParam("x", "1*")
    .WithVerb(HttpMethod.Post)
    .WithContentType("application/json")
    .WithoutHeader("my-header-*")
    .WithRequestBody("{\"a\":*,\"b\":*}")
    .Times(3);

Times(n)允许你断言调用被执行了特定的次数;否则,当进行一个或多个匹配调用时,将传递断言。在所有可以传递名称和值的情况下,null值(默认值)意味着忽略并仅断言名称。和测试设置标准一样,*通配符几乎到处都被支持。

With*方法不能提供您需要的所有内容时,您可以向下一层并直接断言调用日志:

Assert.That(httpTest.CallLog.Any(call => /* 断言当前调用 */));

CallLog是一个IList<FlurlCall>集合。一个FlurlCall对象包含了很多的有用信息,详情参考这里

版权声明:本作品系原创,版权归码友网所有,如未经许可,禁止任何形式转载,违者必究。

发表评论

登录用户才能发表评论, 请 登 录 或者 注册