From 17ed4b11cf6416709b8a137fbf9bbeb3463ebee1 Mon Sep 17 00:00:00 2001 From: Kevin Logan Date: Tue, 23 Oct 2018 11:41:12 -0500 Subject: [PATCH 1/7] starting point for a presentation API test demo. see script in my blog --- README.md | 14 +- presentationSnippets/ApixuClient.txt | 27 ++++ presentationSnippets/ApixuClientTests.txt | 65 ++++++++ presentationSnippets/WeatherController.txt | 36 +++++ .../WeatherControllerTests.txt | 140 ++++++++++++++++++ source/api.tests/Weather/ApixuClientTests.cs | 51 ------- .../Weather/WeatherControllerTests.cs | 136 +---------------- source/api/Weather/ApixuClient.cs | 28 +--- source/api/Weather/WeatherController.cs | 35 +---- 9 files changed, 286 insertions(+), 246 deletions(-) create mode 100644 presentationSnippets/ApixuClient.txt create mode 100644 presentationSnippets/ApixuClientTests.txt create mode 100644 presentationSnippets/WeatherController.txt create mode 100644 presentationSnippets/WeatherControllerTests.txt diff --git a/README.md b/README.md index b61ea5e..93d4f26 100644 --- a/README.md +++ b/README.md @@ -28,8 +28,9 @@ Master branch is the up-to-date version. * master - has the latest running code * emptyStart - after running the commands above with a few tweaks -* apiHttpCall - creating the API tests, merged to master +* apiHttpCall - creating the API tests, merged to master with PR1 * inMemoryDatabase - EF using inMemoryDatabase for tests +* presentationStart - emptied out Weather classes and tests with snippet references ## "Business" Goals @@ -53,13 +54,19 @@ The ride information to save and returns. Mirrors the DB structure with EF Core. ### API > used in my presentation +Given an API call +When asking for current temp +Then it calls the weather Api with the correct zip code + +Examples +57105 | 52 +00000 | 52 // TODO validate zipcodes -- get current temp to fill in ride info Given an API call When asking for current temp Then it calls the weather Api with the correct zip code - Given a new ride is submitted When missing values (invalid) Then it should return a 503 with a invalid message @@ -72,11 +79,12 @@ Then it should persist to the data store with those values Examples - ### Web ### TypeScript tests +These haven't been implemented yet. + Given the new ride screen When missing values (invalid) And Save is clicked diff --git a/presentationSnippets/ApixuClient.txt b/presentationSnippets/ApixuClient.txt new file mode 100644 index 0000000..5cc2fc7 --- /dev/null +++ b/presentationSnippets/ApixuClient.txt @@ -0,0 +1,27 @@ +ApixuClient + + + + public async Task GetCurrentTempAsync(int zipCode) + { + var response = await httpClient.GetStringAsync($"current.json?key={apiKey}&q={zipCode}"); + var weather = ApiuxWeatherCurrentResponse.FromJson(response); + return weather.Current.TempF; + } + + public async Task GetPastWeatherAsync(int zipCode, DateTime dateTime) + { + var response = await httpClient.GetStringAsync($"history.json?key={apiKey}&q={zipCode}&date={dateTime}"); + return ApiuxWeatherForecastResponse.FromJson(response); + } + + //public async Task GetPastWeather_Untestable(int zipCode, DateTime dateTime) + //{ + // using(var httpClient = new HttpClient()) + // { + // httpClient.BaseAddress = new Uri($"https://api.apixu.com/v1/"); + // httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + // var response = await httpClient.GetStringAsync($"past.json?key={apiKey}&q={zipCode}&date={dateTime}"); + // return ApiuxWeatherResponse.FromJson(response); + // } + //} \ No newline at end of file diff --git a/presentationSnippets/ApixuClientTests.txt b/presentationSnippets/ApixuClientTests.txt new file mode 100644 index 0000000..bd647ae --- /dev/null +++ b/presentationSnippets/ApixuClientTests.txt @@ -0,0 +1,65 @@ + [TestClass] + [TestCategory(TestCategories.WeatherAPI)] + public class ApixuClientTests + { + private (ApixuClient apiuxClient, MockHttpMessageHandler mockHttp) Factory() + { + var fakeHttpClient = new Mock(); + + // using https://github.com/richardszalay/mockhttp + var mockHttp = new MockHttpMessageHandler(); + var httpClient = mockHttp.ToHttpClient(); + var apiuxClient = new ApixuClient(httpClient); + return (apiuxClient, mockHttp); + } + + [TestMethod] + public async Task ApixuClientTests_GetTemp_GivenAZipCode_ReturnsTemp() + { + // Arrange + var (apiuxClient, mockHttp) = Factory(); + const int zipCode = 57105; + const double fakeTemp = 70.5; + var response = new ApiuxWeatherCurrentResponse + { + Current = new ApiuxWeatherCurrent + { + TempF = fakeTemp + } + }; + var requestUri = $"https://api.apixu.com/v1/current.json?key={ApixuClient.apiKey}&q={zipCode}"; + mockHttp.When(requestUri) + .Respond("application/json", Serialize.ToJson(response)); + + // Act + var result = await apiuxClient.GetCurrentTempAsync(zipCode); + + // Assert + Assert.AreEqual(fakeTemp, result); + } + + [TestMethod] + public async Task ApixuClientTests_GetTemp_GivenAZipCode_UsesThatZipCode() + { + // Arrange + var (apiuxClient, mockHttp) = Factory(); + const int zipCode = 57105; + const double fakeTemp = 70.5; + var response = new ApiuxWeatherCurrentResponse + { + Current = new ApiuxWeatherCurrent + { + TempF = fakeTemp + } + }; + var requestUri = $"https://api.apixu.com/v1/current.json?key={ApixuClient.apiKey}&q={zipCode}"; + var request = mockHttp.When(requestUri) + .Respond("application/json", Serialize.ToJson(response)); + + + // Act + var result = await apiuxClient.GetCurrentTempAsync(zipCode); + + // Assert + Assert.AreEqual(1, mockHttp.GetMatchCount(request)); + } \ No newline at end of file diff --git a/presentationSnippets/WeatherController.txt b/presentationSnippets/WeatherController.txt new file mode 100644 index 0000000..151825c --- /dev/null +++ b/presentationSnippets/WeatherController.txt @@ -0,0 +1,36 @@ + + private readonly IGetWeatherHttpClient weatherHttpClient; + + public WeatherController(IGetWeatherHttpClient weatherHttpClient) + { + this.weatherHttpClient = weatherHttpClient; + } + + [HttpGet("[action]")] + public async Task> CurrentTemp([FromQuery(Name = "zipcode")] int zipCode) + { + if (zipCode == 0) + { + return BadRequest($"{nameof(zipCode)} cannot be 0"); + } + + var result = await weatherHttpClient.GetCurrentTempAsync(zipCode); + return Ok(result); + } + + [HttpGet("[action]")] + public async Task> PastWeather([FromQuery(Name = "zipcode")] int zipCode, [FromQuery(Name = "dateTime")] string dateTime) + { + if(zipCode == 0) + { + return BadRequest($"{nameof(zipCode)} cannot be 0"); + } + + if (string.IsNullOrWhiteSpace(dateTime) || !DateTime.TryParse(dateTime, out var parsedDateTime)) + { + return BadRequest($"{nameof(dateTime)} must be a valid date"); + } + + var weather = await weatherHttpClient.GetPastWeatherAsync(zipCode, parsedDateTime); + return weather; + } \ No newline at end of file diff --git a/presentationSnippets/WeatherControllerTests.txt b/presentationSnippets/WeatherControllerTests.txt new file mode 100644 index 0000000..def9aee --- /dev/null +++ b/presentationSnippets/WeatherControllerTests.txt @@ -0,0 +1,140 @@ +TestClass] + [TestCategory(TestCategories.WeatherAPI)] + public class WeatherControllerTests + { + private (WeatherController weatherController, Mock getWeatherHttpClient) Factory() + { + var getWeatherHttpClient = new Mock(); + return (new WeatherController(getWeatherHttpClient.Object), getWeatherHttpClient); + } + + [TestMethod] + public async Task WeatherController_GetCurrentTemp_NoZipCode_Returns400() + { + // Arrange + var (weatherController, getWeatherHttpClient) = Factory(); + + // Act + var result = await weatherController.CurrentTemp(0); + + // Assert + Assert.AreEqual(400, (result.Result as BadRequestObjectResult).StatusCode); + } + + [TestMethod] + public async Task WeatherController_GetCurrentTemp_ZipCode_CallsWithZipCode() + { + /** + * Given an API call + * When asking for current temp + * Then it calls the weather Api with the correct zip code + */ + // Arrange + var zipCode = 57105; + var (weatherController, getWeatherHttpClient) = Factory(); + + // fake the return from weather provider with MOQ + var fakeTemp = 72.6; + getWeatherHttpClient.Setup(wp => wp.GetCurrentTempAsync(zipCode)) + .ReturnsAsync(fakeTemp); + + // Act + var response = await weatherController.CurrentTemp(zipCode); + + // Assert + getWeatherHttpClient.Verify(w => w.GetCurrentTempAsync(zipCode), Times.Once); + } + + [TestMethod] + public async Task WeatherController_GetTemp_NoZipCode_Returns400() + { + // Arrange + var (weatherController, getWeatherHttpClient) = Factory(); + + // Act + var result = await weatherController.CurrentTemp(0); + + // Assert + Assert.AreEqual(400, (result.Result as BadRequestObjectResult).StatusCode); + } + + [TestMethod] + public async Task WeatherController_GetPastTemp_NoZipCode_Returns400() + { + // Arrange + var (weatherController, getWeatherHttpClient) = Factory(); + + // Act + var result = await weatherController.PastWeather(0, string.Empty); + + // Assert + Assert.AreEqual(400, (result.Result as BadRequestObjectResult).StatusCode); + } + + [TestMethod] + [DataRow("")] + [DataRow("00-1-2 10:00:00")] + public async Task WeatherController_GetPastTemp_NoDateTimeOrInvalid_Returns400(string dateTime) + { + // Arrange + var (weatherController, getWeatherHttpClient) = Factory(); + + // Act + var result = await weatherController.PastWeather(59785, string.Empty); + + // Assert + Assert.AreEqual(400, (result.Result as BadRequestObjectResult).StatusCode); + } + + [TestMethod] + public async Task WeatherController_GetPastTemp_ZipCodeAndDate_CallsProviderWithZipCodeAndDate() + { + /** + * Given an API call + * When asking for past temp + * Then it calls the weather Api with the correct zip code and date time + */ + // Arrange + var zipCode = 57105; + var date = DateTime.Now; + var (weatherController, getWeatherHttpClient) = Factory(); + + // fake the return from weather provider with MOQ + var fakeTemp = 72.6; + var fakeResponse = new ApiuxWeatherForecastResponse + { + Forecast = new ApiuxWeatherForecast + { + ForecastDay = new List { + new ForecastDay + { + Hour = new List { + new ForecastHour + { + FeelslikeF = fakeTemp + } + } + } + } + } + }; + getWeatherHttpClient.Setup(wp => wp.GetPastWeatherAsync(It.IsAny(), It.IsAny())) + //It.Is(d => d.ToString() == date.ToString()))) + .ReturnsAsync(fakeResponse); + + // Act + var result = await weatherController.PastWeather(zipCode, date.ToString()); + + // Assert + getWeatherHttpClient.Verify(w => w.GetPastWeatherAsync(zipCode, + // needs to use It.Is straight date doesn't match + It.Is(d => d.ToString() == date.ToString())), + Times.Once); + + // this really only tests the MOQ + //var weatherResult = ApiuxWeatherCurrentResponse.FromJson((result.Result as OkObjectResult).Value as string); + //Assert.AreEqual(fakeResponse.Current.TempF, weatherResult.Current.TempF); + } + + // TODO add more tests and code if formatting the data response to abstract from Apiux is required + } \ No newline at end of file diff --git a/source/api.tests/Weather/ApixuClientTests.cs b/source/api.tests/Weather/ApixuClientTests.cs index 35fe379..56ce9a3 100644 --- a/source/api.tests/Weather/ApixuClientTests.cs +++ b/source/api.tests/Weather/ApixuClientTests.cs @@ -23,56 +23,5 @@ public class ApixuClientTests var apiuxClient = new ApixuClient(httpClient); return (apiuxClient, mockHttp); } - - [TestMethod] - public async Task ApixuClientTests_GetTemp_GivenAZipCode_ReturnsTemp() - { - // Arrange - var (apiuxClient, mockHttp) = Factory(); - const int zipCode = 57105; - const double fakeTemp = 70.5; - var response = new ApiuxWeatherCurrentResponse - { - Current = new ApiuxWeatherCurrent - { - TempF = fakeTemp - } - }; - var requestUri = $"https://api.apixu.com/v1/current.json?key={ApixuClient.apiKey}&q={zipCode}"; - mockHttp.When(requestUri) - .Respond("application/json", Serialize.ToJson(response)); - - // Act - var result = await apiuxClient.GetCurrentTempAsync(zipCode); - - // Assert - Assert.AreEqual(fakeTemp, result); - } - - [TestMethod] - public async Task ApixuClientTests_GetTemp_GivenAZipCode_UsesThatZipCode() - { - // Arrange - var (apiuxClient, mockHttp) = Factory(); - const int zipCode = 57105; - const double fakeTemp = 70.5; - var response = new ApiuxWeatherCurrentResponse - { - Current = new ApiuxWeatherCurrent - { - TempF = fakeTemp - } - }; - var requestUri = $"https://api.apixu.com/v1/current.json?key={ApixuClient.apiKey}&q={zipCode}"; - var request = mockHttp.When(requestUri) - .Respond("application/json", Serialize.ToJson(response)); - - - // Act - var result = await apiuxClient.GetCurrentTempAsync(zipCode); - - // Assert - Assert.AreEqual(1, mockHttp.GetMatchCount(request)); - } } } diff --git a/source/api.tests/Weather/WeatherControllerTests.cs b/source/api.tests/Weather/WeatherControllerTests.cs index 1b41ca1..20d9138 100644 --- a/source/api.tests/Weather/WeatherControllerTests.cs +++ b/source/api.tests/Weather/WeatherControllerTests.cs @@ -15,140 +15,8 @@ public class WeatherControllerTests private (WeatherController weatherController, Mock getWeatherHttpClient) Factory() { var getWeatherHttpClient = new Mock(); - return (new WeatherController(getWeatherHttpClient.Object), getWeatherHttpClient); + //return (new WeatherController(getWeatherHttpClient.Object), getWeatherHttpClient); + return (new WeatherController(), getWeatherHttpClient); } - - [TestMethod] - public async Task WeatherController_GetCurrentTemp_NoZipCode_Returns400() - { - // Arrange - var (weatherController, getWeatherHttpClient) = Factory(); - - // Act - var result = await weatherController.CurrentTemp(0); - - // Assert - Assert.AreEqual(400, (result.Result as BadRequestObjectResult).StatusCode); - } - - [TestMethod] - public async Task WeatherController_GetCurrentTemp_ZipCode_CallsWithZipCode() - { - /** - * Given an API call - * When asking for current temp - * Then it calls the weather Api with the correct zip code - */ - // Arrange - var zipCode = 57105; - var (weatherController, getWeatherHttpClient) = Factory(); - - // fake the return from weather provider with MOQ - var fakeTemp = 72.6; - getWeatherHttpClient.Setup(wp => wp.GetCurrentTempAsync(zipCode)) - .ReturnsAsync(fakeTemp); - - // Act - var response = await weatherController.CurrentTemp(zipCode); - - // Assert - getWeatherHttpClient.Verify(w => w.GetCurrentTempAsync(zipCode), Times.Once); - - // Note this test may not be the most useful, but it helps move us forward with TDD - // some may just delete this test or skip writting it - } - - [TestMethod] - public async Task WeatherController_GetTemp_NoZipCode_Returns400() - { - // Arrange - var (weatherController, getWeatherHttpClient) = Factory(); - - // Act - var result = await weatherController.CurrentTemp(0); - - // Assert - Assert.AreEqual(400, (result.Result as BadRequestObjectResult).StatusCode); - } - - [TestMethod] - public async Task WeatherController_GetPastTemp_NoZipCode_Returns400() - { - // Arrange - var (weatherController, getWeatherHttpClient) = Factory(); - - // Act - var result = await weatherController.PastWeather(0, string.Empty); - - // Assert - Assert.AreEqual(400, (result.Result as BadRequestObjectResult).StatusCode); - } - - [TestMethod] - [DataRow("")] - [DataRow("00-1-2 10:00:00")] - public async Task WeatherController_GetPastTemp_NoDateTimeOrInvalid_Returns400(string dateTime) - { - // Arrange - var (weatherController, getWeatherHttpClient) = Factory(); - - // Act - var result = await weatherController.PastWeather(59785, string.Empty); - - // Assert - Assert.AreEqual(400, (result.Result as BadRequestObjectResult).StatusCode); - } - - [TestMethod] - public async Task WeatherController_GetPastTemp_ZipCodeAndDate_CallsProviderWithZipCodeAndDate() - { - /** - * Given an API call - * When asking for past temp - * Then it calls the weather Api with the correct zip code and date time - */ - // Arrange - var zipCode = 57105; - var date = DateTime.Now; - var (weatherController, getWeatherHttpClient) = Factory(); - - // fake the return from weather provider with MOQ - var fakeTemp = 72.6; - var fakeResponse = new ApiuxWeatherForecastResponse - { - Forecast = new ApiuxWeatherForecast - { - ForecastDay = new List { - new ForecastDay - { - Hour = new List { - new ForecastHour - { - FeelslikeF = fakeTemp - } - } - } - } - } - }; - getWeatherHttpClient.Setup(wp => wp.GetPastWeatherAsync(It.IsAny(), It.IsAny())) - //It.Is(d => d.ToString() == date.ToString()))) - .ReturnsAsync(fakeResponse); - - // Act - var result = await weatherController.PastWeather(zipCode, date.ToString()); - - // Assert - getWeatherHttpClient.Verify(w => w.GetPastWeatherAsync(zipCode, - // needs to use It.Is straight date doesn't match - It.Is(d => d.ToString() == date.ToString())), - Times.Once); - - // this really only tests the MOQ - //var weatherResult = ApiuxWeatherCurrentResponse.FromJson((result.Result as OkObjectResult).Value as string); - //Assert.AreEqual(fakeResponse.Current.TempF, weatherResult.Current.TempF); - } - - // TODO add more tests and code if formatting the data response to abstract from Apiux is required } } diff --git a/source/api/Weather/ApixuClient.cs b/source/api/Weather/ApixuClient.cs index 6cd1ed6..bb440fa 100644 --- a/source/api/Weather/ApixuClient.cs +++ b/source/api/Weather/ApixuClient.cs @@ -7,8 +7,8 @@ namespace Api.Weather { public interface IGetWeatherHttpClient { - Task GetCurrentTempAsync(int zipCode); - Task GetPastWeatherAsync(int zip, DateTime dateTime); + //Task GetCurrentTempAsync(int zipCode); + //Task GetPastWeatherAsync(int zip, DateTime dateTime); } public class ApixuClient : IGetWeatherHttpClient @@ -24,29 +24,5 @@ public ApixuClient(HttpClient httpClient) .Add(new MediaTypeWithQualityHeaderValue("application/json")); this.httpClient = httpClient; } - - public async Task GetCurrentTempAsync(int zipCode) - { - var response = await httpClient.GetStringAsync($"current.json?key={apiKey}&q={zipCode}"); - var weather = ApiuxWeatherCurrentResponse.FromJson(response); - return weather.Current.TempF; - } - - public async Task GetPastWeatherAsync(int zipCode, DateTime dateTime) - { - var response = await httpClient.GetStringAsync($"history.json?key={apiKey}&q={zipCode}&date={dateTime}"); - return ApiuxWeatherForecastResponse.FromJson(response); - } - - //public async Task GetPastWeather_Untestable(int zipCode, DateTime dateTime) - //{ - // using(var httpClient = new HttpClient()) - // { - // httpClient.BaseAddress = new Uri($"https://api.apixu.com/v1/"); - // httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); - // var response = await httpClient.GetStringAsync($"past.json?key={apiKey}&q={zipCode}&date={dateTime}"); - // return ApiuxWeatherResponse.FromJson(response); - // } - //} } } diff --git a/source/api/Weather/WeatherController.cs b/source/api/Weather/WeatherController.cs index ec888fa..30104d3 100644 --- a/source/api/Weather/WeatherController.cs +++ b/source/api/Weather/WeatherController.cs @@ -7,40 +7,11 @@ namespace Api.Weather [Route("api/[controller]")] public class WeatherController : Controller { - private readonly IGetWeatherHttpClient weatherHttpClient; + private readonly IGetWeatherHttpClient getWeatherHttpClient; - public WeatherController(IGetWeatherHttpClient weatherHttpClient) + public WeatherController(IGetWeatherHttpClient getWeatherHttpClient) { - this.weatherHttpClient = weatherHttpClient; - } - - [HttpGet("[action]")] - public async Task> CurrentTemp([FromQuery(Name = "zipcode")] int zipCode) - { - if (zipCode == 0) - { - return BadRequest($"{nameof(zipCode)} cannot be 0"); - } - - var result = await weatherHttpClient.GetCurrentTempAsync(zipCode); - return Ok(result); - } - - [HttpGet("[action]")] - public async Task> PastWeather([FromQuery(Name = "zipcode")] int zipCode, [FromQuery(Name = "dateTime")] string dateTime) - { - if(zipCode == 0) - { - return BadRequest($"{nameof(zipCode)} cannot be 0"); - } - - if (string.IsNullOrWhiteSpace(dateTime) || !DateTime.TryParse(dateTime, out var parsedDateTime)) - { - return BadRequest($"{nameof(dateTime)} must be a valid date"); - } - - var weather = await weatherHttpClient.GetPastWeatherAsync(zipCode, parsedDateTime); - return weather; + this.getWeatherHttpClient = getWeatherHttpClient; } } From 04844d76fc347f75c9d7e45a849e7d1c6cbd4ad7 Mon Sep 17 00:00:00 2001 From: Kevin Logan Date: Wed, 24 Oct 2018 08:28:03 -0500 Subject: [PATCH 2/7] better starting point for weather tests --- README.md | 5 +---- source/api.tests/Weather/WeatherControllerTests.cs | 7 ++----- source/api/Weather/ApixuClient.cs | 8 ++++++-- source/api/Weather/WeatherController.cs | 10 ++++++---- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 93d4f26..0ad991b 100644 --- a/README.md +++ b/README.md @@ -6,8 +6,6 @@ Example code for [my aligneddev.net article](https://www.aligneddev.net/blog/201 I hope to show an example at my presentation and people will walk out thinking "I can do that". -Master branch is the up-to-date version. - ## Getting started - create the projects in master * Get the [latest Vue template](https://github.com/MarkPieszak/AspNETCore-Vue-starter) before starting. @@ -117,5 +115,4 @@ Examples ### MVC Tests ?? any needed with SPA approach?? -[my article on "classic" MVC testing](https://www.aligneddev.net/blog/2018/unit-test-mvc/) - +[my article on "classic" MVC testing](https://www.aligneddev.net/blog/2018/unit-test-mvc/) \ No newline at end of file diff --git a/source/api.tests/Weather/WeatherControllerTests.cs b/source/api.tests/Weather/WeatherControllerTests.cs index 20d9138..6f4e246 100644 --- a/source/api.tests/Weather/WeatherControllerTests.cs +++ b/source/api.tests/Weather/WeatherControllerTests.cs @@ -12,11 +12,8 @@ namespace Api.Tests.Weather [TestCategory(TestCategories.WeatherAPI)] public class WeatherControllerTests { - private (WeatherController weatherController, Mock getWeatherHttpClient) Factory() - { - var getWeatherHttpClient = new Mock(); - //return (new WeatherController(getWeatherHttpClient.Object), getWeatherHttpClient); - return (new WeatherController(), getWeatherHttpClient); + private WeatherController Factory(){ + return new WeatherController(); } } } diff --git a/source/api/Weather/ApixuClient.cs b/source/api/Weather/ApixuClient.cs index bb440fa..186e936 100644 --- a/source/api/Weather/ApixuClient.cs +++ b/source/api/Weather/ApixuClient.cs @@ -7,8 +7,7 @@ namespace Api.Weather { public interface IGetWeatherHttpClient { - //Task GetCurrentTempAsync(int zipCode); - //Task GetPastWeatherAsync(int zip, DateTime dateTime); + Task GetCurrentTempAsync(int zipCode); } public class ApixuClient : IGetWeatherHttpClient @@ -24,5 +23,10 @@ public ApixuClient(HttpClient httpClient) .Add(new MediaTypeWithQualityHeaderValue("application/json")); this.httpClient = httpClient; } + + public Task GetCurrentTempAsync(int zipCode) + { + throw new NotImplementedException(); + } } } diff --git a/source/api/Weather/WeatherController.cs b/source/api/Weather/WeatherController.cs index 30104d3..67bbbc0 100644 --- a/source/api/Weather/WeatherController.cs +++ b/source/api/Weather/WeatherController.cs @@ -7,12 +7,14 @@ namespace Api.Weather [Route("api/[controller]")] public class WeatherController : Controller { - private readonly IGetWeatherHttpClient getWeatherHttpClient; + public WeatherController() { - public WeatherController(IGetWeatherHttpClient getWeatherHttpClient) - { - this.getWeatherHttpClient = getWeatherHttpClient; } + // private readonly IGetWeatherHttpClient getWeatherHttpClient; + // public WeatherController(IGetWeatherHttpClient getWeatherHttpClient) + // { + // this.getWeatherHttpClient = getWeatherHttpClient; + // } } } From be32358a2f0358c1dc5ea3f37dd4320a31d7e667 Mon Sep 17 00:00:00 2001 From: Kevin Logan Date: Wed, 24 Oct 2018 08:28:40 -0500 Subject: [PATCH 3/7] removed emptyStart --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 0ad991b..29c7a2e 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,6 @@ I hope to show an example at my presentation and people will walk out thinking " ## Branches * master - has the latest running code -* emptyStart - after running the commands above with a few tweaks * apiHttpCall - creating the API tests, merged to master with PR1 * inMemoryDatabase - EF using inMemoryDatabase for tests * presentationStart - emptied out Weather classes and tests with snippet references From 6374ea5ae62592bf9d7e62c5be79a6be8d1f17c0 Mon Sep 17 00:00:00 2001 From: Kevin Logan Date: Wed, 24 Oct 2018 12:44:51 -0500 Subject: [PATCH 4/7] cleanup usings --- source/api.tests/Weather/ApixuClientTests.cs | 53 ++++++++++++++++++- .../MockHttpClientHandlerExtensions.cs | 24 +++------ .../Weather/WeatherControllerTests.cs | 7 +-- 3 files changed, 60 insertions(+), 24 deletions(-) diff --git a/source/api.tests/Weather/ApixuClientTests.cs b/source/api.tests/Weather/ApixuClientTests.cs index 56ce9a3..767ead1 100644 --- a/source/api.tests/Weather/ApixuClientTests.cs +++ b/source/api.tests/Weather/ApixuClientTests.cs @@ -1,9 +1,7 @@ using Api.Weather; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; -using Moq.Protected; using RichardSzalay.MockHttp; -using System; using System.Net.Http; using System.Threading.Tasks; @@ -23,5 +21,56 @@ public class ApixuClientTests var apiuxClient = new ApixuClient(httpClient); return (apiuxClient, mockHttp); } + + [TestMethod] + public async Task ApixuClientTests_GetTemp_GivenAZipCode_ReturnsTemp() + { + // Arrange + var (apiuxClient, mockHttp) = Factory(); + const int zipCode = 57105; + const double fakeTemp = 70.5; + var response = new ApiuxWeatherCurrentResponse + { + Current = new ApiuxWeatherCurrent + { + TempF = fakeTemp + } + }; + var requestUri = $"https://api.apixu.com/v1/current.json?key={ApixuClient.apiKey}&q={zipCode}"; + mockHttp.When(requestUri) + .Respond("application/json", Serialize.ToJson(response)); + + // Act + var result = await apiuxClient.GetCurrentTempAsync(zipCode); + + // Assert + Assert.AreEqual(fakeTemp, result); + } + + [TestMethod] + public async Task ApixuClientTests_GetTemp_GivenAZipCode_UsesThatZipCode() + { + // Arrange + var (apiuxClient, mockHttp) = Factory(); + const int zipCode = 57105; + const double fakeTemp = 70.5; + var response = new ApiuxWeatherCurrentResponse + { + Current = new ApiuxWeatherCurrent + { + TempF = fakeTemp + } + }; + var requestUri = $"https://api.apixu.com/v1/current.json?key={ApixuClient.apiKey}&q={zipCode}"; + var request = mockHttp.When(requestUri) + .Respond("application/json", Serialize.ToJson(response)); + + + // Act + var result = await apiuxClient.GetCurrentTempAsync(zipCode); + + // Assert + Assert.AreEqual(1, mockHttp.GetMatchCount(request)); + } } } diff --git a/source/api.tests/Weather/MockHttpClientHandlerExtensions.cs b/source/api.tests/Weather/MockHttpClientHandlerExtensions.cs index 0dd46e4..bebdd05 100644 --- a/source/api.tests/Weather/MockHttpClientHandlerExtensions.cs +++ b/source/api.tests/Weather/MockHttpClientHandlerExtensions.cs @@ -1,12 +1,4 @@ -using Moq; -using Moq.Protected; -using System; -using System.Net; -using System.Net.Http; -using System.Threading; -using System.Threading.Tasks; - -namespace Api.Tests.Weather +namespace Api.Tests.Weather { // use https://github.com/richardszalay/mockhttp instead /// @@ -16,13 +8,13 @@ namespace Api.Tests.Weather //{ // public static void SetupGetStringAsync(this Mock mockHandler, string response, Uri requestUri) // { - // if you have to be specific or multiple calls in the same method change or want to verify it - //mockHandler.Protected() - // .Setup>("SendAsync", - // ItExpr.Is(message => message.RequestUri == requestUri), - // ItExpr.IsAny()) - // .Returns(Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(response) })) - // .Verifiable(); + // if you have to be specific or multiple calls in the same method change or want to verify it + //mockHandler.Protected() + // .Setup>("SendAsync", + // ItExpr.Is(message => message.RequestUri == requestUri), + // ItExpr.IsAny()) + // .Returns(Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(response) })) + // .Verifiable(); // } //} } diff --git a/source/api.tests/Weather/WeatherControllerTests.cs b/source/api.tests/Weather/WeatherControllerTests.cs index 6f4e246..93c72fa 100644 --- a/source/api.tests/Weather/WeatherControllerTests.cs +++ b/source/api.tests/Weather/WeatherControllerTests.cs @@ -1,10 +1,5 @@ using Api.Weather; -using Microsoft.AspNetCore.Mvc; using Microsoft.VisualStudio.TestTools.UnitTesting; -using Moq; -using System; -using System.Collections.Generic; -using System.Threading.Tasks; namespace Api.Tests.Weather { @@ -12,7 +7,7 @@ namespace Api.Tests.Weather [TestCategory(TestCategories.WeatherAPI)] public class WeatherControllerTests { - private WeatherController Factory(){ + private WeatherController Factory() { return new WeatherController(); } } From 853dfcb84afea89f55c130cb8eaa98a39056e441 Mon Sep 17 00:00:00 2001 From: Kevin Logan Date: Wed, 24 Oct 2018 12:45:23 -0500 Subject: [PATCH 5/7] cleanup more usings --- source/api/Weather/ApiuxWeatherForecastResponse.cs | 2 -- source/api/Weather/WeatherController.cs | 2 -- 2 files changed, 4 deletions(-) diff --git a/source/api/Weather/ApiuxWeatherForecastResponse.cs b/source/api/Weather/ApiuxWeatherForecastResponse.cs index 12d681d..890b14d 100644 --- a/source/api/Weather/ApiuxWeatherForecastResponse.cs +++ b/source/api/Weather/ApiuxWeatherForecastResponse.cs @@ -1,8 +1,6 @@ using Newtonsoft.Json; using System; using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; namespace Api.Weather { diff --git a/source/api/Weather/WeatherController.cs b/source/api/Weather/WeatherController.cs index 67bbbc0..0a3eef4 100644 --- a/source/api/Weather/WeatherController.cs +++ b/source/api/Weather/WeatherController.cs @@ -1,6 +1,4 @@ using Microsoft.AspNetCore.Mvc; -using System; -using System.Threading.Tasks; namespace Api.Weather { From 38af059a009f55efa73aef3c7db79238c984c612 Mon Sep 17 00:00:00 2001 From: Kevin Logan Date: Wed, 24 Oct 2018 12:55:00 -0500 Subject: [PATCH 6/7] empty out ApixuClientTests --- source/api.tests/Weather/ApixuClientTests.cs | 51 -------------------- 1 file changed, 51 deletions(-) diff --git a/source/api.tests/Weather/ApixuClientTests.cs b/source/api.tests/Weather/ApixuClientTests.cs index 767ead1..c18f7b9 100644 --- a/source/api.tests/Weather/ApixuClientTests.cs +++ b/source/api.tests/Weather/ApixuClientTests.cs @@ -21,56 +21,5 @@ public class ApixuClientTests var apiuxClient = new ApixuClient(httpClient); return (apiuxClient, mockHttp); } - - [TestMethod] - public async Task ApixuClientTests_GetTemp_GivenAZipCode_ReturnsTemp() - { - // Arrange - var (apiuxClient, mockHttp) = Factory(); - const int zipCode = 57105; - const double fakeTemp = 70.5; - var response = new ApiuxWeatherCurrentResponse - { - Current = new ApiuxWeatherCurrent - { - TempF = fakeTemp - } - }; - var requestUri = $"https://api.apixu.com/v1/current.json?key={ApixuClient.apiKey}&q={zipCode}"; - mockHttp.When(requestUri) - .Respond("application/json", Serialize.ToJson(response)); - - // Act - var result = await apiuxClient.GetCurrentTempAsync(zipCode); - - // Assert - Assert.AreEqual(fakeTemp, result); - } - - [TestMethod] - public async Task ApixuClientTests_GetTemp_GivenAZipCode_UsesThatZipCode() - { - // Arrange - var (apiuxClient, mockHttp) = Factory(); - const int zipCode = 57105; - const double fakeTemp = 70.5; - var response = new ApiuxWeatherCurrentResponse - { - Current = new ApiuxWeatherCurrent - { - TempF = fakeTemp - } - }; - var requestUri = $"https://api.apixu.com/v1/current.json?key={ApixuClient.apiKey}&q={zipCode}"; - var request = mockHttp.When(requestUri) - .Respond("application/json", Serialize.ToJson(response)); - - - // Act - var result = await apiuxClient.GetCurrentTempAsync(zipCode); - - // Assert - Assert.AreEqual(1, mockHttp.GetMatchCount(request)); - } } } From f109af7bbdf46c82403ed248e1895a7859ec5bf5 Mon Sep 17 00:00:00 2001 From: Kevin Logan Date: Thu, 25 Oct 2018 12:55:30 -0500 Subject: [PATCH 7/7] snippets and walkthrough are good! --- presentationSnippets/WalkthroughSnippets.md | 99 +++++++++++++++++++++ presentationSnippets/WeatherController.txt | 1 - 2 files changed, 99 insertions(+), 1 deletion(-) create mode 100644 presentationSnippets/WalkthroughSnippets.md diff --git a/presentationSnippets/WalkthroughSnippets.md b/presentationSnippets/WalkthroughSnippets.md new file mode 100644 index 0000000..65b8172 --- /dev/null +++ b/presentationSnippets/WalkthroughSnippets.md @@ -0,0 +1,99 @@ +## Snippets + +### Create WeatherControllerTests.WeatherController_GetCurrentTemp_NoZipCode_Returns400 + +[TestMethod] +public async Task WeatherController_GetCurrentTemp_NoZipCode_Returns400() +{ + /** + * Given an API call + * When asking for current temp and no zip code is given + * Then returns a 400 + **/ +} + +var controller = Factory(); + +var result = await controller.CurrentTemp(0); + +Assert.Inconclusive(); + +> in WeatherController + public Task> CurrentTemp(int zipCode) +{ + return Task.FromResult(52.5); +} + +> WeatherControllerTests +Assert.AreEqual(52.5, result); + +then to fail +Assert.AreEqual(400, (result.Result as BadRequestObjectResult).StatusCode); + +> in WeatherController + public async Task> CurrentTemp(int zipCode) +{ + if (zipCode == 0) + { + return BadRequest($"{nameof(zipCode)} cannot be 0"); + } + + return Ok(10); +} + +*** Test passes *** +#### Next Test - WeatherController_GetCurrentTemp_ZipCode_CallsWithZipCode + + [TestMethod] +public async Task WeatherController_GetCurrentTemp_ZipCode_CallsWithZipCode() +{ + /** + * Given an API call + * When asking for current temp + * Then it calls the weather Api with the correct zip code + */ + + // Arrange + var controller = Factory(); + + // Act + + // Assert +} + +// Arrange +var zipCode = 57105; +var (controller, getWeatherHttpClient) = Factory(); + +update others with this too + +// Assert +Assert.IsTrue(false); + +private (WeatherController weatherController, Mock getWeatherHttpClient) Factory() +{ + var getWeatherHttpClient = new Mock(); + return (new WeatherController(getWeatherHttpClient.Object), getWeatherHttpClient); +} + +> Startup.cs needs // already there +services.AddHttpClient(); + +> Fake the response +// Arrange +var fakeTemp = 72.6; +getWeatherHttpClient.Setup(wp => wp.GetCurrentTempAsync(zipCode)).ReturnsAsync(fakeTemp); + + +> Test +// Act +var response = await controller.CurrentTemp(zipCode); + + + +// Assert +getWeatherHttpClient.Verify(w => w.GetCurrentTempAsync(zipCode), Times.Once); + +> Controller - add call to the client +var result = await weatherHttpClient.GetCurrentTempAsync(zipCode); +return Ok(result); \ No newline at end of file diff --git a/presentationSnippets/WeatherController.txt b/presentationSnippets/WeatherController.txt index 151825c..4d277bd 100644 --- a/presentationSnippets/WeatherController.txt +++ b/presentationSnippets/WeatherController.txt @@ -6,7 +6,6 @@ this.weatherHttpClient = weatherHttpClient; } - [HttpGet("[action]")] public async Task> CurrentTemp([FromQuery(Name = "zipcode")] int zipCode) { if (zipCode == 0)