From c7bc34600f9f110605887e164e8a2d1239fd8464 Mon Sep 17 00:00:00 2001 From: Todd Menier Date: Fri, 24 Jun 2016 16:04:35 -0500 Subject: [PATCH] not-so-minor overhaul of cookies --- .../Http/ClientConfigTests.cs | 36 ++++----- .../Http/FlurlClientTests.cs | 2 +- Test/Flurl.Test.Shared/Http/GetTests.cs | 6 +- .../Http/GlobalConfigTests.cs | 7 +- Test/Flurl.Test.Shared/Http/RealHttpTests.cs | 28 +++++-- Test/Flurl.Test.Shared/Http/TestingTests.cs | 20 +++++ .../Configuration/FlurlHttpSettings.cs | 6 ++ src/Flurl.Http.Shared/CookieExtensions.cs | 28 ++----- src/Flurl.Http.Shared/FlurlClient.cs | 57 +++++++++++++- src/Flurl.Http.Shared/Testing/HttpTest.cs | 77 +++++++++++-------- 10 files changed, 182 insertions(+), 85 deletions(-) diff --git a/Test/Flurl.Test.Shared/Http/ClientConfigTests.cs b/Test/Flurl.Test.Shared/Http/ClientConfigTests.cs index 4e27c40..8fdb390 100644 --- a/Test/Flurl.Test.Shared/Http/ClientConfigTests.cs +++ b/Test/Flurl.Test.Shared/Http/ClientConfigTests.cs @@ -94,7 +94,7 @@ namespace Flurl.Test.Http { using (var test = new HttpTest()) { - test.RespondWith(404, "Nothing to see here"); + test.RespondWith("Nothing to see here", 404); // no exception = pass await "http://www.api.com" .AllowHttpStatus(HttpStatusCode.Conflict, HttpStatusCode.NotFound) @@ -107,7 +107,7 @@ namespace Flurl.Test.Http { using (var test = new HttpTest()) { - test.RespondWith(418, "I'm a teapot"); + test.RespondWith("I'm a teapot", 418); // allow 4xx var client = "http://www.api.com".AllowHttpStatus("4xx"); // but then disallow it @@ -121,7 +121,7 @@ namespace Flurl.Test.Http { using (var test = new HttpTest()) { - test.RespondWith(500, "epic fail"); + test.RespondWith("epic fail", 500); try { var result = await "http://www.api.com".AllowAnyHttpStatus().GetAsync(); @@ -140,7 +140,7 @@ namespace Flurl.Test.Http using (var test = new HttpTest()) { FlurlHttp.GlobalSettings.AllowedHttpStatusRange = "*"; - test.RespondWith(500, "epic fail"); + test.RespondWith("epic fail", 500); Assert.ThrowsAsync(async () => await "http://www.api.com".ConfigureClient(c => c.AllowedHttpStatusRange = "2xx").GetAsync()); } } @@ -153,9 +153,9 @@ namespace Flurl.Test.Http var client3 = client1.WithUrl("http://www.api.com/for-client3"); var client4 = client2.WithUrl("http://www.api.com/for-client4"); - CollectionAssert.AreEquivalent(client1.GetCookies(), client2.GetCookies()); - CollectionAssert.AreEquivalent(client1.GetCookies(), client3.GetCookies()); - CollectionAssert.AreEquivalent(client1.GetCookies(), client4.GetCookies()); + CollectionAssert.AreEquivalent(client1.Cookies, client2.Cookies); + CollectionAssert.AreEquivalent(client1.Cookies, client3.Cookies); + CollectionAssert.AreEquivalent(client1.Cookies, client4.Cookies); var urls = new[] { client1, client2, client3, client4 }.Select(c => c.Url.ToString()); CollectionAssert.AllItemsAreUnique(urls); } @@ -171,11 +171,11 @@ namespace Flurl.Test.Http client2.Dispose(); client3.Dispose(); - CollectionAssert.IsEmpty(client2.GetCookies()); - CollectionAssert.IsEmpty(client3.GetCookies()); + CollectionAssert.IsEmpty(client2.Cookies); + CollectionAssert.IsEmpty(client3.Cookies); - CollectionAssert.IsNotEmpty(client1.GetCookies()); - CollectionAssert.IsNotEmpty(client4.GetCookies()); + CollectionAssert.IsNotEmpty(client1.Cookies); + CollectionAssert.IsNotEmpty(client4.Cookies); } [Test] @@ -186,9 +186,9 @@ namespace Flurl.Test.Http var client3 = "http://www.api.com/for-client3".WithClient(client1); var client4 = "http://www.api.com/for-client4".WithClient(client1); - CollectionAssert.AreEquivalent(client1.GetCookies(), client2.GetCookies()); - CollectionAssert.AreEquivalent(client1.GetCookies(), client3.GetCookies()); - CollectionAssert.AreEquivalent(client1.GetCookies(), client4.GetCookies()); + CollectionAssert.AreEquivalent(client1.Cookies, client2.Cookies); + CollectionAssert.AreEquivalent(client1.Cookies, client3.Cookies); + CollectionAssert.AreEquivalent(client1.Cookies, client4.Cookies); var urls = new[] { client1, client2, client3, client4 }.Select(c => c.Url.ToString()); CollectionAssert.AllItemsAreUnique(urls); } @@ -204,11 +204,11 @@ namespace Flurl.Test.Http client2.Dispose(); client3.Dispose(); - CollectionAssert.IsEmpty(client2.GetCookies()); - CollectionAssert.IsEmpty(client3.GetCookies()); + CollectionAssert.IsEmpty(client2.Cookies); + CollectionAssert.IsEmpty(client3.Cookies); - CollectionAssert.IsNotEmpty(client1.GetCookies()); - CollectionAssert.IsNotEmpty(client4.GetCookies()); + CollectionAssert.IsNotEmpty(client1.Cookies); + CollectionAssert.IsNotEmpty(client4.Cookies); } [Test] diff --git a/Test/Flurl.Test.Shared/Http/FlurlClientTests.cs b/Test/Flurl.Test.Shared/Http/FlurlClientTests.cs index 93ff47f..9d92b00 100644 --- a/Test/Flurl.Test.Shared/Http/FlurlClientTests.cs +++ b/Test/Flurl.Test.Shared/Http/FlurlClientTests.cs @@ -14,7 +14,7 @@ namespace Flurl.Test.Http var fcExts = ReflectionHelper.GetAllExtensionMethods(typeof(FlurlClient).GetTypeInfo().Assembly); var urlExts = ReflectionHelper.GetAllExtensionMethods(typeof(FlurlClient).GetTypeInfo().Assembly); var stringExts = ReflectionHelper.GetAllExtensionMethods(typeof(FlurlClient).GetTypeInfo().Assembly); - var whitelist = new[] { "GetCookies", "WithUrl" }; // cases where Url method of the same name was excluded intentionally + var whitelist = new[] { "WithUrl" }; // cases where Url method of the same name was excluded intentionally foreach (var method in fcExts) { if (whitelist.Contains(method.Name)) diff --git a/Test/Flurl.Test.Shared/Http/GetTests.cs b/Test/Flurl.Test.Shared/Http/GetTests.cs index dd5f701..8625cb3 100644 --- a/Test/Flurl.Test.Shared/Http/GetTests.cs +++ b/Test/Flurl.Test.Shared/Http/GetTests.cs @@ -85,7 +85,7 @@ namespace Flurl.Test.Http [Test] public async Task failure_throws_detailed_exception() { - HttpTest.RespondWith(500, "bad job"); + HttpTest.RespondWith("bad job", status: 500); try { await "http://api.com".GetStringAsync(); @@ -101,7 +101,7 @@ namespace Flurl.Test.Http [Test] public async Task can_get_error_json_typed() { - HttpTest.RespondWithJson(500, new { code = 999, message = "our server crashed" }); + HttpTest.RespondWithJson(new { code = 999, message = "our server crashed" }, 500); try { await "http://api.com".GetStringAsync(); @@ -116,7 +116,7 @@ namespace Flurl.Test.Http [Test] public async Task can_get_error_json_untyped() { - HttpTest.RespondWithJson(500, new { code = 999, message = "our server crashed" }); + HttpTest.RespondWithJson(new { code = 999, message = "our server crashed" }, 500); try { await "http://api.com".GetStringAsync(); diff --git a/Test/Flurl.Test.Shared/Http/GlobalConfigTests.cs b/Test/Flurl.Test.Shared/Http/GlobalConfigTests.cs index 8444b0b..23df732 100644 --- a/Test/Flurl.Test.Shared/Http/GlobalConfigTests.cs +++ b/Test/Flurl.Test.Shared/Http/GlobalConfigTests.cs @@ -1,5 +1,4 @@ using System; -using System.Net; using System.Net.Http; using System.Threading.Tasks; using Flurl.Http; @@ -41,7 +40,7 @@ namespace Flurl.Test.Http public async Task can_allow_non_success_status() { using (var test = new HttpTest()) { GetSettings().AllowedHttpStatusRange = "4xx"; - test.RespondWith(418, "I'm a teapot"); + test.RespondWith("I'm a teapot", 418); try { var result = await GetClient().GetAsync(); Assert.IsFalse(result.IsSuccessStatusCode); @@ -87,7 +86,7 @@ namespace Flurl.Test.Http public async Task can_set_error_callback(bool markExceptionHandled) { var callbackCalled = false; using (var test = new HttpTest()) { - test.RespondWith(500, "server error"); + test.RespondWith("server error", 500); GetSettings().OnError = call => { CollectionAssert.IsEmpty(test.ResponseQueue); // verifies that callback is running after HTTP call is made callbackCalled = true; @@ -112,7 +111,7 @@ namespace Flurl.Test.Http GetSettings().OnError = call => { call.ExceptionHandled = true; }; - test.RespondWith(500, "server error"); + test.RespondWith("server error", 500); try { var result = await GetClient().GetAsync(); Assert.IsFalse(result.IsSuccessStatusCode); diff --git a/Test/Flurl.Test.Shared/Http/RealHttpTests.cs b/Test/Flurl.Test.Shared/Http/RealHttpTests.cs index 9b0801d..7a69675 100644 --- a/Test/Flurl.Test.Shared/Http/RealHttpTests.cs +++ b/Test/Flurl.Test.Shared/Http/RealHttpTests.cs @@ -27,7 +27,7 @@ namespace Flurl.Test.Http } [Test] - public async Task can_set_cookies() { + public async Task can_set_request_cookies() { var resp = await "http://httpbin.org/cookies".WithCookies(new { x = 1, y = 2 }).GetJsonAsync(); // httpbin returns json representation of cookies that were set on the server. @@ -35,15 +35,29 @@ namespace Flurl.Test.Http Assert.AreEqual("2", resp.cookies.y); } + [Test] + public async Task can_set_cookies_before_setting_url() { + var fc = new FlurlClient().WithCookie("z", "999"); + var resp = await fc.WithUrl("http://httpbin.org/cookies").GetJsonAsync(); + Assert.AreEqual("999", resp.cookies.z); + } + + [Test] + public async Task can_get_response_cookies() { + var fc = new FlurlClient().EnableCookies(); + await fc.WithUrl("https://httpbin.org/cookies/set?z=999").HeadAsync(); + Assert.AreEqual("999", fc.Cookies["z"].Value); + } + [Test] public async Task cant_persist_cookies_without_resuing_client() { var fc = "http://httpbin.org/cookies".WithCookie("z", 999); // cookie should be set - Assert.AreEqual("999", fc.GetCookies()["z"].Value); + Assert.AreEqual("999", fc.Cookies["z"].Value); await fc.HeadAsync(); // FlurlClient was auto-disposed, so cookie should be gone - Assert.IsFalse(fc.GetCookies().ContainsKey("z")); + CollectionAssert.IsEmpty(fc.Cookies); // httpbin returns json representation of cookies that were set on the server. var resp = await "http://httpbin.org/cookies".GetJsonAsync(); @@ -55,13 +69,13 @@ namespace Flurl.Test.Http using (var fc = new FlurlClient()) { var fc2 = "http://httpbin.org/cookies".WithClient(fc).WithCookie("z", 999); // cookie should be set - Assert.AreEqual("999", fc.GetCookies()["z"].Value); - Assert.AreEqual("999", fc2.GetCookies()["z"].Value); + Assert.AreEqual("999", fc.Cookies["z"].Value); + Assert.AreEqual("999", fc2.Cookies["z"].Value); await fc2.HeadAsync(); // FlurlClient should be re-used, so cookie should stick - Assert.AreEqual("999", fc.GetCookies()["z"].Value); - Assert.AreEqual("999", fc2.GetCookies()["z"].Value); + Assert.AreEqual("999", fc.Cookies["z"].Value); + Assert.AreEqual("999", fc2.Cookies["z"].Value); // httpbin returns json representation of cookies that were set on the server. var resp = await "http://httpbin.org/cookies".WithClient(fc).GetJsonAsync(); diff --git a/Test/Flurl.Test.Shared/Http/TestingTests.cs b/Test/Flurl.Test.Shared/Http/TestingTests.cs index 29df48e..fa4cfee 100644 --- a/Test/Flurl.Test.Shared/Http/TestingTests.cs +++ b/Test/Flurl.Test.Shared/Http/TestingTests.cs @@ -75,5 +75,25 @@ namespace Flurl.Test.Http StringAssert.Contains("timed out", ex.Message); } } + + [Test] + public async Task can_fake_headers() { + HttpTest.RespondWith(headers: new { h1 = "foo" }); + + var resp = await "http://www.api.com".GetAsync(); + Assert.AreEqual(1, resp.Headers.Count()); + Assert.AreEqual("h1", resp.Headers.First().Key); + Assert.AreEqual("foo", resp.Headers.First().Value.First()); + } + + [Test] + public async Task can_fake_cookies() { + HttpTest.RespondWith(cookies: new { c1 = "foo" }); + + var fc = "http://www.api.com".EnableCookies(); + await fc.GetAsync(); + Assert.AreEqual(1, fc.Cookies.Count()); + Assert.AreEqual("foo", fc.Cookies["c1"].Value); + } } } diff --git a/src/Flurl.Http.Shared/Configuration/FlurlHttpSettings.cs b/src/Flurl.Http.Shared/Configuration/FlurlHttpSettings.cs index 46bb45d..bba8ddb 100644 --- a/src/Flurl.Http.Shared/Configuration/FlurlHttpSettings.cs +++ b/src/Flurl.Http.Shared/Configuration/FlurlHttpSettings.cs @@ -28,6 +28,11 @@ namespace Flurl.Http.Configuration /// public string AllowedHttpStatusRange { get; set; } + /// + /// Gets or sets a value indicating whether cookies should be sent/received with each HTTP request. + /// + public bool CookiesEnabled { get; set; } + /// /// Gets or sets a factory used to create HttpClient object used in Flurl HTTP calls. Default value /// is an instance of DefaultHttpClientFactory. Custom factory implementations should generally @@ -84,6 +89,7 @@ namespace Flurl.Http.Configuration public void ResetDefaults() { DefaultTimeout = new HttpClient().Timeout; AllowedHttpStatusRange = null; + CookiesEnabled = false; HttpClientFactory = new DefaultHttpClientFactory(); JsonSerializer = new NewtonsoftJsonSerializer(null); UrlEncodedSerializer = new DefaultUrlEncodedSerializer(); diff --git a/src/Flurl.Http.Shared/CookieExtensions.cs b/src/Flurl.Http.Shared/CookieExtensions.cs index 365d718..6fe312b 100644 --- a/src/Flurl.Http.Shared/CookieExtensions.cs +++ b/src/Flurl.Http.Shared/CookieExtensions.cs @@ -12,18 +12,14 @@ namespace Flurl.Http /// public static class CookieExtensions { - /// - /// Gets a collection of cookies that will be sent in calls using this client. (Use FlurlClient.WithCookie/WithCookies to set cookies.) - /// - public static Dictionary GetCookies(this FlurlClient client) { - return GetCookieContainer(client)?.GetCookies(client.HttpClient.BaseAddress).Cast().ToDictionary(c => c.Name, c => c); - } + + /// /// Allows cookies to be sent and received in calls made with this client. Not necessary to call when setting cookies via WithCookie/WithCookies. /// public static FlurlClient EnableCookies(this FlurlClient client) { - GetCookieContainer(client); // ensures the container has been created + client.Settings.CookiesEnabled = true; return client; } @@ -49,7 +45,8 @@ namespace Flurl.Http /// The modified FlurlClient. /// is null. public static FlurlClient WithCookie(this FlurlClient client, Cookie cookie) { - GetCookieContainer(client).Add(client.HttpClient.BaseAddress, cookie); + client.Settings.CookiesEnabled = true; + client.Cookies[cookie.Name] = cookie; return client; } @@ -75,7 +72,6 @@ namespace Flurl.Http return new FlurlClient(url, true).WithCookie(cookie); } - /// /// Sets an HTTP cookie to be sent with all requests made with this FlurlClient. /// @@ -86,7 +82,8 @@ namespace Flurl.Http /// The modified FlurlClient. /// is null. public static FlurlClient WithCookie(this FlurlClient client, string name, object value, DateTime? expires = null) { - return client.WithCookie(new Cookie(name, value?.ToInvariantString()) { Expires = expires ?? DateTime.MinValue }); + var cookie = new Cookie(name, value?.ToInvariantString()) { Expires = expires ?? DateTime.MinValue }; + return client.WithCookie(cookie); } /// @@ -156,16 +153,5 @@ namespace Flurl.Http public static FlurlClient WithCookies(this string url, object cookies, DateTime? expires = null) { return new FlurlClient(url, true).WithCookies(cookies); } - - private static CookieContainer GetCookieContainer(FlurlClient client) { - var handler = client.HttpMessageHandler as HttpClientHandler; - if (handler == null) - return null; - - if (client.HttpClient.BaseAddress == null) - client.HttpClient.BaseAddress = new Uri(Url.GetRoot(client.Url)); - - return handler.CookieContainer ?? (handler.CookieContainer = new CookieContainer()); - } } } \ No newline at end of file diff --git a/src/Flurl.Http.Shared/FlurlClient.cs b/src/Flurl.Http.Shared/FlurlClient.cs index 83bae14..9a2f4ba 100644 --- a/src/Flurl.Http.Shared/FlurlClient.cs +++ b/src/Flurl.Http.Shared/FlurlClient.cs @@ -1,4 +1,7 @@ using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; using System.Net.Http; using System.Threading; using System.Threading.Tasks; @@ -59,6 +62,7 @@ namespace Flurl.Http _parent = this, Settings = Settings, Url = Url, + Cookies = Cookies, AutoDispose = AutoDispose }; } @@ -77,6 +81,11 @@ namespace Flurl.Http /// public Url Url { get; set; } + /// + /// Collection of HttpCookies sent and received. + /// + public IDictionary Cookies { get; private set; } = new Dictionary(); + /// /// Gets a value indicating whether the underlying HttpClient /// should be disposed immediately after the first HTTP call is made. @@ -109,14 +118,59 @@ namespace Flurl.Http public async Task SendAsync(HttpMethod verb, HttpContent content = null, CancellationToken? cancellationToken = null, HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) { try { var request = new HttpRequestMessage(verb, Url) { Content = content }; + if (Settings.CookiesEnabled) + WriteRequestCookies(request); HttpCall.Set(request, Settings); - return await HttpClient.SendAsync(request, completionOption, cancellationToken ?? CancellationToken.None).ConfigureAwait(false); + var resp = await HttpClient.SendAsync(request, completionOption, cancellationToken ?? CancellationToken.None).ConfigureAwait(false); + if (Settings.CookiesEnabled) + ReadResponseCookies(resp); + return resp; } finally { if (AutoDispose) Dispose(); } } + private void WriteRequestCookies(HttpRequestMessage request) { + if (!Cookies.Any()) return; + var uri = request.RequestUri; + var cookieHandler = HttpMessageHandler as HttpClientHandler; + + // if the inner handler is an HttpClientHandler (which it usually is), put the cookies in the CookieContainer. + if (cookieHandler != null) { + if (cookieHandler.CookieContainer == null) + cookieHandler.CookieContainer = new CookieContainer(); + foreach (var cookie in Cookies.Values) + cookieHandler.CookieContainer.Add(uri, cookie); + } + else { + // http://stackoverflow.com/a/15588878/62600 + request.Headers.TryAddWithoutValidation("Cookie", string.Join("; ", Cookies.Values)); + } + } + + private void ReadResponseCookies(HttpResponseMessage response) { + var uri = response.RequestMessage.RequestUri; + + // if the inner handler is an HttpClientHandler (which it usually is), it's already plucked the + // cookies out of the headers and put them in the CookieContainer. + var jar = (HttpMessageHandler as HttpClientHandler)?.CookieContainer; + if (jar == null) { + // http://stackoverflow.com/a/15588878/62600 + IEnumerable cookieHeaders; + if (!response.Headers.TryGetValues("Set-Cookie", out cookieHeaders)) + return; + + jar = new CookieContainer(); + foreach (string header in cookieHeaders) { + jar.SetCookies(uri, header); + } + } + + foreach (var cookie in jar.GetCookies(uri).Cast()) + Cookies[cookie.Name] = cookie; + } + /// /// Gets the HttpMessageHandler to be used in subsequent HTTP calls. Creation (when necessary) is delegated /// to FlurlHttp.HttpClientFactory. @@ -142,6 +196,7 @@ namespace Flurl.Http _httpClient?.Dispose(); _httpMessageHandler = null; _httpClient = null; + Cookies = new Dictionary(); } } } \ No newline at end of file diff --git a/src/Flurl.Http.Shared/Testing/HttpTest.cs b/src/Flurl.Http.Shared/Testing/HttpTest.cs index a953865..3add943 100644 --- a/src/Flurl.Http.Shared/Testing/HttpTest.cs +++ b/src/Flurl.Http.Shared/Testing/HttpTest.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Net; using System.Net.Http; using Flurl.Http.Content; +using Flurl.Util; namespace Flurl.Http.Testing { @@ -32,41 +33,57 @@ namespace Flurl.Http.Testing } /// - /// Adds an HttpResponseMessage to the response queue with the given HTTP status code and content body. + /// Adds an HttpResponseMessage to the response queue. /// - public HttpTest RespondWith(int status, string body) { - ResponseQueue.Enqueue(new HttpResponseMessage { + /// The simulated response body string. + /// The simulated HTTP status. Default is 200. + /// The simulated response headers (optional). + /// The simulated response cookies (optional). + /// The current HttpTest object (so more responses can be chained). + public HttpTest RespondWith(string body, int status = 200, object headers = null, object cookies = null) { + return RespondWith(new StringContent(body), status, headers, cookies); + } + + /// + /// Adds an HttpResponseMessage to the response queue with the given data serialized to JSON as the content body. + /// + /// The object to be JSON-serialized and used as the simulated response body. + /// The simulated HTTP status. Default is 200. + /// The simulated response headers (optional). + /// The simulated response cookies (optional). + /// The current HttpTest object (so more responses can be chained). + public HttpTest RespondWithJson(object body, int status = 200, object headers = null, object cookies = null) { + var content = new CapturedJsonContent(FlurlHttp.GlobalSettings.JsonSerializer.Serialize(body)); + return RespondWith(content, status, headers, cookies); + } + + /// + /// Adds an HttpResponseMessage to the response queue. + /// + /// The simulated response body content (optional). + /// The simulated HTTP status. Default is 200. + /// The simulated response headers (optional). + /// The simulated response cookies (optional). + /// The current HttpTest object (so more responses can be chained). + public HttpTest RespondWith(HttpContent content = null, int status = 200, object headers = null, object cookies = null) { + var response = new HttpResponseMessage { StatusCode = (HttpStatusCode)status, - Content = new StringContent(body) - }); + Content = content + }; + if (headers != null) { + foreach (var kv in headers.ToKeyValuePairs()) + response.Headers.Add(kv.Key, kv.Value.ToInvariantString()); + } + if (cookies != null) { + foreach (var kv in cookies.ToKeyValuePairs()) { + var value = new Cookie(kv.Key, kv.Value.ToInvariantString()).ToString(); + response.Headers.Add("Set-Cookie", value); + } + } + ResponseQueue.Enqueue(response); return this; } - /// - /// Adds an HttpResponseMessage to the response queue with a 200 (OK) status code and the given content body. - /// - public HttpTest RespondWith(string body) { - return RespondWith(200, body); - } - - /// - /// Adds an HttpResponseMessage to the response queue with the given HTTP status code and the given data serialized to JSON as the content body. - /// - public HttpTest RespondWithJson(int status, object data) { - ResponseQueue.Enqueue(new HttpResponseMessage { - StatusCode = (HttpStatusCode)status, - Content = new CapturedJsonContent(FlurlHttp.GlobalSettings.JsonSerializer.Serialize(data)) - }); - return this; - } - - /// - /// Adds an HttpResponseMessage to the response queue with a 200 (OK) status code and the given data serialized to JSON as the content body. - /// - public HttpTest RespondWithJson(object data) { - return RespondWithJson(200, data); - } - /// /// Adds a simulated timeout response to the response queue. ///