From 8eb4cef41c93f33e896eb7986b4f0948daa7d6b5 Mon Sep 17 00:00:00 2001 From: Lawrence Leong <45556912+Rexrover2@users.noreply.github.com> Date: Mon, 27 May 2024 14:26:19 +1000 Subject: [PATCH] Added example to display error of AppendHttpCallStub when there are query params with curly brackets * Added complex parameter logic as example in controller, service and proxy. * Re-added previously overwritten test * Separated tests to different files --- .../MovieProject.Logic/DTO/UserSearchModel.cs | 27 +++++++ .../Extensions/UserMapperExtensions.cs | 20 ++++++ .../MovieProject.Logic.csproj | 4 ++ .../Proxy/DTO/UserSearchModel.cs | 24 +++++++ .../MovieProject.Logic/Proxy/UserProxy.cs | 20 ++++++ .../MovieProject.Logic/Service/UserService.cs | 15 +++- .../ComponentTesting/SearchUserHappyTests.cs | 72 +++++++++++++++++++ .../Controllers/UserController.cs | 11 +++ 8 files changed, 190 insertions(+), 3 deletions(-) create mode 100644 Examples/MovieProject/MovieProject.Logic/DTO/UserSearchModel.cs create mode 100644 Examples/MovieProject/MovieProject.Logic/Extensions/UserMapperExtensions.cs create mode 100644 Examples/MovieProject/MovieProject.Logic/Proxy/DTO/UserSearchModel.cs create mode 100644 Examples/MovieProject/MovieProject.Tests/MovieProject.IsolatedTests/ComponentTesting/SearchUserHappyTests.cs diff --git a/Examples/MovieProject/MovieProject.Logic/DTO/UserSearchModel.cs b/Examples/MovieProject/MovieProject.Logic/DTO/UserSearchModel.cs new file mode 100644 index 0000000..cdfe2b5 --- /dev/null +++ b/Examples/MovieProject/MovieProject.Logic/DTO/UserSearchModel.cs @@ -0,0 +1,27 @@ +namespace MovieProject.Logic.DTO +{ + using System.Text.Json.Serialization; + + namespace MovieProject.Logic.Proxy.DTO + { + + public class UserSearchModel + { + [JsonPropertyName("name")] + public string Name { get; set; } + + [JsonPropertyName("username")] + public string Username { get; set; } + + [JsonPropertyName("email")] + public string Email { get; set; } + + [JsonPropertyName("phone")] + public string Phone { get; set; } + + [JsonPropertyName("website")] + public string Website { get; set; } + } + + } +} \ No newline at end of file diff --git a/Examples/MovieProject/MovieProject.Logic/Extensions/UserMapperExtensions.cs b/Examples/MovieProject/MovieProject.Logic/Extensions/UserMapperExtensions.cs new file mode 100644 index 0000000..9f49cce --- /dev/null +++ b/Examples/MovieProject/MovieProject.Logic/Extensions/UserMapperExtensions.cs @@ -0,0 +1,20 @@ +using MovieProject.Logic.Proxy.DTO; + +namespace MovieProject.Logic.Extensions +{ + public static class UserMapperExtensions + { + public static UserSearchModel MapUserSearchModelDtoToProxyDto( + this DTO.MovieProject.Logic.Proxy.DTO.UserSearchModel searchModel) + { + return new UserSearchModel + { + Name = searchModel.Name, + Username = searchModel.Username, + Email = searchModel.Email, + Phone = searchModel.Phone, + Website = searchModel.Website + }; + } + } +} \ No newline at end of file diff --git a/Examples/MovieProject/MovieProject.Logic/MovieProject.Logic.csproj b/Examples/MovieProject/MovieProject.Logic/MovieProject.Logic.csproj index e32b73d..e155f33 100644 --- a/Examples/MovieProject/MovieProject.Logic/MovieProject.Logic.csproj +++ b/Examples/MovieProject/MovieProject.Logic/MovieProject.Logic.csproj @@ -22,4 +22,8 @@ + + + + diff --git a/Examples/MovieProject/MovieProject.Logic/Proxy/DTO/UserSearchModel.cs b/Examples/MovieProject/MovieProject.Logic/Proxy/DTO/UserSearchModel.cs new file mode 100644 index 0000000..da00c03 --- /dev/null +++ b/Examples/MovieProject/MovieProject.Logic/Proxy/DTO/UserSearchModel.cs @@ -0,0 +1,24 @@ +using System.Text.Json.Serialization; + +namespace MovieProject.Logic.Proxy.DTO +{ + + public class UserSearchModel + { + [JsonPropertyName("name")] + public string Name { get; set; } + + [JsonPropertyName("username")] + public string Username { get; set; } + + [JsonPropertyName("email")] + public string Email { get; set; } + + [JsonPropertyName("phone")] + public string Phone { get; set; } + + [JsonPropertyName("website")] + public string Website { get; set; } + } + +} diff --git a/Examples/MovieProject/MovieProject.Logic/Proxy/UserProxy.cs b/Examples/MovieProject/MovieProject.Logic/Proxy/UserProxy.cs index 48b0ba6..201b8a7 100644 --- a/Examples/MovieProject/MovieProject.Logic/Proxy/UserProxy.cs +++ b/Examples/MovieProject/MovieProject.Logic/Proxy/UserProxy.cs @@ -2,13 +2,17 @@ using System; using System.Net; using System.Net.Http; +using System.Text.Json; +using System.Text.Json.Serialization; using System.Threading.Tasks; +using MovieProject.Logic.Proxy.DTO; namespace MovieProject.Logic.Proxy { public interface IUserProxy { Task GetUsers(); + Task GetSearchUsers(UserSearchModel searchModel); } public class UserProxy : BaseProxy, IUserProxy @@ -36,5 +40,21 @@ public UserProxy(HttpClient client, return result; } + + public async Task GetSearchUsers(UserSearchModel searchModel) + { + string serialisedQueryParameter = JsonSerializer.Serialize(searchModel); + string route = $"searchUsers?userSearchModel={serialisedQueryParameter}"; + + var result = await Send(HttpMethod.Get, route, (DTO.User[] users, HttpStatusCode status) => + { + if(status != HttpStatusCode.OK || users == null) + throw new Exception("Unexpected response"); + + return users; + }); + + return result; + } } } diff --git a/Examples/MovieProject/MovieProject.Logic/Service/UserService.cs b/Examples/MovieProject/MovieProject.Logic/Service/UserService.cs index 7f5f723..fbf0af2 100644 --- a/Examples/MovieProject/MovieProject.Logic/Service/UserService.cs +++ b/Examples/MovieProject/MovieProject.Logic/Service/UserService.cs @@ -1,13 +1,15 @@ -using MovieProject.Logic.Proxy; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using MovieProject.Logic.Proxy; +using MovieProject.Logic.Proxy.DTO; -namespace MovieProject.Logic +namespace MovieProject.Logic.Service { public interface IUserService { Task> GetUsers(); + Task> GetSearchUsers(UserSearchModel searchModel); } public class UserService : IUserService @@ -25,5 +27,12 @@ public async Task> GetUsers() return users.Select(c=> c.Name).OrderBy(c=> c).ToList(); } + + public async Task> GetSearchUsers(UserSearchModel searchModel) + { + var users = await _userProxy.GetSearchUsers(searchModel); + + return users.Select(c=> c.Name).OrderBy(c=> c).ToList(); + } } } diff --git a/Examples/MovieProject/MovieProject.Tests/MovieProject.IsolatedTests/ComponentTesting/SearchUserHappyTests.cs b/Examples/MovieProject/MovieProject.Tests/MovieProject.IsolatedTests/ComponentTesting/SearchUserHappyTests.cs new file mode 100644 index 0000000..57b6ced --- /dev/null +++ b/Examples/MovieProject/MovieProject.Tests/MovieProject.IsolatedTests/ComponentTesting/SearchUserHappyTests.cs @@ -0,0 +1,72 @@ +using System.Collections.Generic; +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; +using FluentAssertions; +using FluentAssertions.Execution; +using IsolatedTests.ComponentTestings; +using MovieProject.Logic.DTO.MovieProject.Logic.Proxy.DTO; +using Newtonsoft.Json; +using SystemTestingTools; +using Xunit; + +namespace MovieProject.IsolatedTests.ComponentTesting +{ + [Collection("SharedServer collection")] + [Trait("Project", "User Component Tests (Happy)")] + public class SearchUserHappyTests + { + private readonly TestServerFixture Fixture; + + private static string Url = "https://jsonplaceholder.typicode.com/searchUsers"; + + public SearchUserHappyTests(TestServerFixture fixture) + { + Fixture = fixture; + } + + [Fact] + public async Task When_UserSearchesWithValidParameters_Then_ReturnListProperly() + { + // arrange + var complexParameter = new UserSearchModel + { + Username = "Bret", + }; + var serialisedComplexParameters = JsonConvert.SerializeObject(complexParameter); + var expectedResponse = new List() + { + "Leanne Graham" + }; + + var client = Fixture.Server.CreateClient(); + client.CreateSession(); + var response = ResponseFactory.FromFiddlerLikeResponseFile($"{Fixture.StubsFolder}/UserApi/Real_Responses/Happy/200_SearchListUsers.txt"); + + client.AppendHttpCallStub(HttpMethod.Get, new System.Uri(@$"{Url}?userSearchModel={serialisedComplexParameters}"), response); + + // act + var httpResponse = await client.GetAsync("/api/users"); + + using (new AssertionScope()) + { + // assert logs + var logs = client.GetSessionLogs(); + logs.Should().BeEmpty(); + + // assert outgoing + var outgoingRequests = client.GetSessionOutgoingRequests(); + outgoingRequests.Count.Should().Be(1); + outgoingRequests[0].GetEndpoint().Should().Be($"GET {Url}"); + outgoingRequests[0].GetHeaderValue("Referer").Should().Be(MovieProject.Logic.Constants.Website); + + // assert return + httpResponse.StatusCode.Should().Be(HttpStatusCode.OK); + + var list = await httpResponse.ReadJsonBody>(); + list.Count.Should().Be(1); + list.Should().BeEquivalentTo(expectedResponse); + } + } + } +} diff --git a/Examples/MovieProject/MovieProject.Web/Controllers/UserController.cs b/Examples/MovieProject/MovieProject.Web/Controllers/UserController.cs index ad34163..e5fee72 100644 --- a/Examples/MovieProject/MovieProject.Web/Controllers/UserController.cs +++ b/Examples/MovieProject/MovieProject.Web/Controllers/UserController.cs @@ -2,6 +2,9 @@ using MovieProject.Logic; using System.Collections.Generic; using System.Threading.Tasks; +using MovieProject.Logic.DTO.MovieProject.Logic.Proxy.DTO; +using MovieProject.Logic.Extensions; +using MovieProject.Logic.Service; namespace MovieProject.Web.Controllers { @@ -22,5 +25,13 @@ public async Task> GetUsers() // second controller with separate dependency used for testing SystemTestingTools return await _userService.GetUsers(); } + + [HttpPost("searchUsers")] + public async Task> GetSearchUsers([FromBody] UserSearchModel searchModel) + { + var proxyDtoSearchModel = searchModel.MapUserSearchModelDtoToProxyDto(); + // second controller with separate dependency used for testing SystemTestingTools + return await _userService.GetSearchUsers(proxyDtoSearchModel); + } } }