From ef3aba3c81e53888f830608ce12797f3d26ba699 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20N=C3=A4geli?= Date: Fri, 3 Dec 2021 18:02:59 +0100 Subject: [PATCH] Implement additional functionality (#3) --- MockH.Tests/MockH.Tests.csproj | 2 + MockH.Tests/RedirectTests.cs | 41 ++++++++++ MockH.Tests/RespondTests.cs | 27 +++++++ MockH.Tests/RunTests.cs | 128 ++++++++++++++++++++++++++++++++ MockH.Tests/ServerTest.cs | 17 ++++- MockH.sln | 7 +- MockH/Builder/IncompleteRule.cs | 8 ++ MockH/MockH.csproj | 4 +- README.md | 51 ++++++++++++- 9 files changed, 279 insertions(+), 6 deletions(-) create mode 100644 MockH.Tests/RedirectTests.cs create mode 100644 MockH.Tests/RespondTests.cs create mode 100644 MockH.Tests/RunTests.cs diff --git a/MockH.Tests/MockH.Tests.csproj b/MockH.Tests/MockH.Tests.csproj index 4840d53..f30e8bc 100644 --- a/MockH.Tests/MockH.Tests.csproj +++ b/MockH.Tests/MockH.Tests.csproj @@ -15,6 +15,8 @@ + + diff --git a/MockH.Tests/RedirectTests.cs b/MockH.Tests/RedirectTests.cs new file mode 100644 index 0000000..2836952 --- /dev/null +++ b/MockH.Tests/RedirectTests.cs @@ -0,0 +1,41 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Net; + +namespace MockH.Tests +{ + + [TestClass] + public class RedirectTests : ServerTest + { + + [TestMethod] + public async Task RulesCanRedirectTemporarily() + { + using var server = MockServer.Run + ( + On.Get().Redirect("https://www.google.de") + ); + + using var response = await GetAsync(server); + + Assert.AreEqual(HttpStatusCode.TemporaryRedirect, response.StatusCode); + Assert.AreEqual(new Uri("https://www.google.de"), response.Headers.Location); + } + + [TestMethod] + public async Task RulesCanRedirectPermanently() + { + using var server = MockServer.Run + ( + On.Get().Redirect("https://www.google.de", temporary: false) + ); + + using var response = await GetAsync(server); + + Assert.AreEqual(HttpStatusCode.Moved, response.StatusCode); + Assert.AreEqual(new Uri("https://www.google.de"), response.Headers.Location); + } + + } + +} diff --git a/MockH.Tests/RespondTests.cs b/MockH.Tests/RespondTests.cs new file mode 100644 index 0000000..612de60 --- /dev/null +++ b/MockH.Tests/RespondTests.cs @@ -0,0 +1,27 @@ +using GenHTTP.Api.Protocol; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Net; + +namespace MockH.Tests +{ + + [TestClass] + public class RespondTests : ServerTest + { + + [TestMethod] + public async Task BasicResponse() + { + using var server = MockServer.Run + ( + On.Get().Respond(ResponseStatus.InternalServerError) + ); + + using var response = await GetAsync(server); + + Assert.AreEqual(HttpStatusCode.InternalServerError, response.StatusCode); + } + + } + +} diff --git a/MockH.Tests/RunTests.cs b/MockH.Tests/RunTests.cs new file mode 100644 index 0000000..0b4eace --- /dev/null +++ b/MockH.Tests/RunTests.cs @@ -0,0 +1,128 @@ +using GenHTTP.Api.Protocol; +using GenHTTP.Modules.DirectoryBrowsing; +using GenHTTP.Modules.IO; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Net; + +namespace MockH.Tests +{ + + [TestClass] + public class RunTests : ServerTest + { + + [TestMethod] + public async Task TestConstant() + { + using var server = MockServer.Run + ( + On.Get().Run(() => 42) + ); + + using var response = await GetAsync(server); + + Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); + Assert.AreEqual("42", await response.Content.ReadAsStringAsync()); + } + + private record MyClass(int IntValue, string StringValue); + + [TestMethod] + public async Task TestJson() + { + using var server = MockServer.Run + ( + On.Get().Run(() => new MyClass(42, "The answer")) + ); + + using var response = await GetAsync(server); + + Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); + Assert.AreEqual("{\"intValue\":42,\"stringValue\":\"The answer\"}", await response.Content.ReadAsStringAsync()); + } + + [TestMethod] + public async Task TestQuery() + { + using var server = MockServer.Run + ( + On.Get().Run((int i) => i + 1) + ); + + using var response = await GetAsync(server, "/?i=1"); + + Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); + Assert.AreEqual("2", await response.Content.ReadAsStringAsync()); + } + + [TestMethod] + public async Task TestPath() + { + using var server = MockServer.Run + ( + On.Get("/increment/:i").Run((int i) => i + 1) + ); + + using var response = await GetAsync(server, "/increment/1"); + + Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); + Assert.AreEqual("2", await response.Content.ReadAsStringAsync()); + } + + [TestMethod] + public async Task TestPost() + { + using var server = MockServer.Run + ( + On.Post().Run((MyClass body) => body) + ); + + using var response = await PostAsync(server, "{\"intValue\":42,\"stringValue\":\"The answer\"}"); + + Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); + Assert.AreEqual("{\"intValue\":42,\"stringValue\":\"The answer\"}", await response.Content.ReadAsStringAsync()); + } + + [TestMethod] + public async Task TestStream() + { + using var server = MockServer.Run + ( + On.Post().Run((Stream body) => body.Length) + ); + + using var response = await PostAsync(server, "{\"intValue\":42,\"stringValue\":\"The answer\"}"); + + Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); + Assert.AreEqual("42" /* :D */, await response.Content.ReadAsStringAsync()); + } + + [TestMethod] + public async Task TestRequest() + { + using var server = MockServer.Run + ( + On.Get().Run((IRequest request) => request.Respond().Status(ResponseStatus.BadRequest)) + ); + + using var response = await GetAsync(server); + + Assert.AreEqual(HttpStatusCode.BadRequest, response.StatusCode); + } + + [TestMethod] + public async Task TestHandler() + { + using var server = MockServer.Run + ( + On.Get().Run(() => Listing.From(ResourceTree.FromDirectory("./"))) + ); + + using var response = await GetAsync(server); + + Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); + } + + } + +} diff --git a/MockH.Tests/ServerTest.cs b/MockH.Tests/ServerTest.cs index 87e9e3a..3142ea3 100644 --- a/MockH.Tests/ServerTest.cs +++ b/MockH.Tests/ServerTest.cs @@ -1,15 +1,30 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using MockH.Environment; +using System.Net.Http.Json; namespace MockH.Tests { public abstract class ServerTest { - protected HttpClient _Client = new(); + protected HttpClient _Client = new(new HttpClientHandler() + { + AllowAutoRedirect = false + }); protected async ValueTask GetStringAsync(Server server, string? path = null) => await _Client.GetStringAsync(server.Url(path)); + + protected async ValueTask GetAsync(Server server, string? path = null) => await _Client.GetAsync(server.Url(path)); + + protected async ValueTask PostAsync(Server server, string value, string? path = null) + { + var content = new StringContent(value); + + content.Headers.ContentType = new("application/json"); + + return await _Client.PostAsync(server.Url(path), content); + } [TestCleanup] public void Cleanup() diff --git a/MockH.sln b/MockH.sln index cafa143..6ee9b30 100644 --- a/MockH.sln +++ b/MockH.sln @@ -5,7 +5,12 @@ VisualStudioVersion = 17.0.31912.275 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MockH", "MockH\MockH.csproj", "{69AB1471-7E7D-4C76-BDD8-58CA8369649F}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MockH.Tests", "MockH.Tests\MockH.Tests.csproj", "{C5EA4883-2BBE-4F46-AC99-465DF6895958}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MockH.Tests", "MockH.Tests\MockH.Tests.csproj", "{C5EA4883-2BBE-4F46-AC99-465DF6895958}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{83377AA8-18D5-4CFF-9AE6-B11070952B2F}" + ProjectSection(SolutionItems) = preProject + README.md = README.md + EndProjectSection EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/MockH/Builder/IncompleteRule.cs b/MockH/Builder/IncompleteRule.cs index 031948c..79679a3 100644 --- a/MockH/Builder/IncompleteRule.cs +++ b/MockH/Builder/IncompleteRule.cs @@ -1,5 +1,7 @@ using GenHTTP.Api.Protocol; +using Basics = GenHTTP.Modules.Basics; + namespace MockH.Builder { @@ -31,6 +33,12 @@ public IncompleteRule(HashSet methods, string? path) public Rule Return(T data) => new(Methods, Path, () => data); + public Rule Redirect(string location, bool temporary = true) => new(Methods, Path, (IRequest request) => Basics.Redirect.To(location).Mode(temporary)); + + public Rule Run(Delegate action) => new(Methods, Path, action); + + public Rule Respond(ResponseStatus status) => new(Methods, Path, (IRequest request) => request.Respond().Status(status).Build()); + #endregion } diff --git a/MockH/MockH.csproj b/MockH/MockH.csproj index 3b22891..cfc8184 100644 --- a/MockH/MockH.csproj +++ b/MockH/MockH.csproj @@ -33,9 +33,9 @@ - + - + diff --git a/README.md b/README.md index ed307ee..c53b511 100644 --- a/README.md +++ b/README.md @@ -6,12 +6,13 @@ This library allows to mock HTTP responses for integration and acceptance tests - Fast and thread safe - Only a few dependencies +- No configuration needed - Does not interfer with Kestrel or ASP.NET - Independent from the testing framework ## Usage -``` +```csharp using MockH; [TestMethod] @@ -19,11 +20,57 @@ public async Task TestSomething() { using var server = MockServer.Run ( - On.Get("/users/1").Return(new User(...)) + On.Get("/users/1").Return(new User(...)), + On.Get("/users/2").Respond(ResponseStatus.NoContent) ); // access the server in your code via HTTP using var client = new HttpClient(); + await client.GetStringAsync(server.Url("/users/1")); } ``` + +## Basic Usage + +```csharp +// return a specific status code +On.Get("/ifail").Respond(ResponseStatus.InternalServerError); + +// redirect the client +On.Get().Redirect("https://github.com"); + +// execute logic and return some simple text value +On.Get().Run(() => 42); + +// execute logic and return some JSON +private record MyClass(int IntValue, string StringValue); + +On.Get().Run(() => new MyClass(42, "The answer")); + +// execute logic asynchronously +On.Get().Run(async () => await ...); + +// access query parameters (GET /increment?=1) +On.Get("/increment").Run((int i) => i + 1); + +// access path parameters (GET /increment/1) +On.Get("/increment/:i").Run((int i) => i + 1); + +// access request body +On.Post().Run((MyClass body) => body); + +// access request body as stream +On.Post().Run((Stream body) => body.Length); +``` + +## Advanced Usage + +```csharp +// directly access request and response +On.Get().Run((IRequest request) => request.Respond().Status(ResponseStatus.BadRequest)); + +// return a handler provided by the GenHTTP framework, e.g. a website (see https://genhttp.org/documentation/content/) +// can be useful if you want to test some kind of website crawler +On.Get().Run(() => Listing.From(ResourceTree.FromDirectory("/var/www"))); +``` \ No newline at end of file