diff --git a/source/API.UnitTests/Weather/WeatherControllerTests.cs b/source/API.UnitTests/Weather/WeatherControllerTests.cs index 6c803b4..22bd52e 100644 --- a/source/API.UnitTests/Weather/WeatherControllerTests.cs +++ b/source/API.UnitTests/Weather/WeatherControllerTests.cs @@ -13,36 +13,96 @@ public class WeatherControllerTests { private (WeatherController weatherController, Mock getWeatherHttpClient) Factory() { + // an alternative to returning the tuple, is to have private properties on this class for the injected classes + // this is useful if you're class/unit under test has too many dependencies + // which means you should probably refactor that class to separate out concerns var getWeatherHttpClient = new Mock(); return (new WeatherController(getWeatherHttpClient.Object), getWeatherHttpClient); } [TestMethod] - public async Task WeatherController_GetCurrentTemp_NoZipCode_Returns400() + public async Task WeatherController_CurrentTemp_NoZipCode_Returns400() + { + // Given an API call + // When asking for current temp and no zip code is given + // Then returns a 400 + Assert.Fail(); + } + + #region first tests + /* + // let's start with the not allowed use case, those are easy to think of + // (work as a team with QA to come up with these before you start coding + // at least before you consider your work as done) + [TestMethod] + public async Task WeatherController_CurrentTemp_NoZipCode_Returns400() { // Arrange var (weatherController, _) = Factory(); + // we won't get to the client call, so no stubs setup needed + // Act var result = await weatherController.CurrentTemp(0); // Assert Assert.AreEqual(400, (result.Result as BadRequestObjectResult).StatusCode); - } + } + */ + /* + [TestMethod] + public async Task WeatherController_CurrentTemp_NoOrBadZipCode_Returns400() + { + //Given an API call + //When asking for current temp and no zip code is given + //Then returns a 400 + // Arrange + var (weatherController, getWeatherHttpClient) = Factory(); + + // Act + var result = await weatherController.CurrentTemp(0); + // Assert + Assert.AreEqual(400, (result.Result as BadRequestObjectResult).StatusCode); + } + */ + /* + // now, let's learn how to use DataRow to get more coverage + // and replace WeatherController_CurrentTemp_NoOrBadZipCode_Returns400 + [TestMethod] + [DataRow(0)] + [DataRow(-57105)] + [DataRow(12)] + [DataRow(1921393)] + public async Task WeatherController_CurrentTemp_NoOrBadZipCode_Returns400(int zipCode) + { + //Given an API call + //When asking for current temp and no zip code is given + //Then returns a 400 + // Arrange + var (weatherController, getWeatherHttpClient) = Factory(); + + // Act + var result = await weatherController.CurrentTemp(zipCode); + + // Assert + Assert.AreEqual(400, (result.Result as BadRequestObjectResult).StatusCode); + } + */ + /* + // this shows how to setup the stub and fake the return from weather provider [TestMethod] - public async Task WeatherController_GetCurrentTemp_ZipCode_CallsWithZipCode() + public async Task WeatherController_CurrentTemp_ValidZipCode_CallsWithZipCode() { - /** - * Given an API call - * When asking for current temp - * Then it calls the weather Api with the correct zip code - */ + // 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 + // setup the stub and fake the return from weather provider with MOQ var fakeTemp = 72.6; getWeatherHttpClient.Setup(wp => wp.GetCurrentTempAsync(zipCode)) .ReturnsAsync(fakeTemp); @@ -53,10 +113,36 @@ public async Task WeatherController_GetCurrentTemp_ZipCode_CallsWithZipCode() // 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 + // 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 + } + */ + /* + // test the happy path + [TestMethod] + public async Task WeatherController_CurrentTemp_ValidZipCode_ReturnsTheTemp() + { + // first make/leave WeatherController return zipcode in result + // Arrange + var zipCode = 57105; + var (weatherController, getWeatherHttpClient) = Factory(); + var fakeTemp = 72.6; + getWeatherHttpClient.Setup(wp => wp.GetCurrentTempAsync(zipCode)) + .ReturnsAsync(fakeTemp); + + // Act + var result = await weatherController.CurrentTemp(zipCode); + + // Assert + Assert.AreEqual(200, (result.Result as OkObjectResult).StatusCode); + Assert.AreEqual(fakeTemp, (result.Result as OkObjectResult).Value); } + */ + // more tests could explore exception handling + #endregion + #region completed tests + /* [TestMethod] public async Task WeatherController_GetTemp_NoZipCode_Returns400() { @@ -71,102 +157,85 @@ public async Task WeatherController_GetTemp_NoZipCode_Returns400() } [TestMethod] - public async Task WeatherController_GetCurrentTemp_ValidZipCode_ReturnsTheTemp() + 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; - getWeatherHttpClient.Setup(wp => wp.GetCurrentTempAsync(zipCode)) - .ReturnsAsync(fakeTemp); + 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.CurrentTemp(zipCode); + var result = await weatherController.PastWeather(zipCode, date.ToString()); // Assert - Assert.AreEqual(200, (result.Result as OkObjectResult).StatusCode); - Assert.AreEqual(fakeTemp, (result.Result as OkObjectResult).Value); + 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); } - // bring in tests and TDD new method - //[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 + */ + #endregion + //// TODO add more tests and code if formatting the data response to abstract from Apiux is required } } diff --git a/source/API.UnitTests/Weather/WeatherStackClientTests.cs b/source/API.UnitTests/Weather/WeatherStackClientTests.cs index 2afd4aa..1320eec 100644 --- a/source/API.UnitTests/Weather/WeatherStackClientTests.cs +++ b/source/API.UnitTests/Weather/WeatherStackClientTests.cs @@ -14,97 +14,105 @@ namespace Api.UnitTests.Weather [TestCategory(TestCategories.WeatherAPI)] public class WeatherStackClientTests { - private (WeatherStackClient apiuxClient, MockHttpMessageHandler mockHttp) Factory() + private (WeatherStackClient weatherStackClient, MockHttpMessageHandler mockHttp) Factory() { var fakeHttpClient = new Mock(); // using https://github.com/richardszalay/mockhttp var mockHttp = new MockHttpMessageHandler(); var httpClient = mockHttp.ToHttpClient(); - var apiuxClient = new WeatherStackClient(httpClient); - return (apiuxClient, mockHttp); + var weatherStackClient = new WeatherStackClient(httpClient); + return (weatherStackClient, mockHttp); } [TestMethod] public async Task GetTemp_GivenAZipCode_ReturnsTemp() { - // Arrange - var (apiuxClient, mockHttp) = Factory(); - const int zipCode = 57105; - const long fakeTemp = (long)70.5; - var response = new WeatherMainResponse - { - Current = new WeatherCurrentResponse - { - Temperature = fakeTemp - } - }; - - var requestUri = $"http://api.weatherstack.com/current?access_key={WeatherStackClient.apiKey}&type=zipcode&units=f&query={zipCode}"; - mockHttp.When(requestUri) - .Respond("application/json", Serialize.ToJson(response)); - - // Act - var result = await apiuxClient.GetCurrentTempAsync(zipCode); - - // Assert - Assert.AreEqual(fakeTemp, result); + Assert.Fail(); + + + //// Arrange + //var (weatherStackClient, mockHttp) = Factory(); + //const int zipCode = 57105; + //const long fakeTemp = (long)70.5; + //var response = new WeatherMainResponse + //{ + // Current = new WeatherCurrentResponse + // { + // Temperature = fakeTemp + // } + //}; + + //var requestUri = $"http://api.weatherstack.com/current?access_key={WeatherStackClient.apiKey}&type=zipcode&units=f&query={zipCode}"; + //mockHttp.When(requestUri) + // .Respond("application/json", Serialize.ToJson(response)); + + //// Act + //var result = await weatherStackClient.GetCurrentTempAsync(zipCode); + + //// Assert + //Assert.AreEqual(fakeTemp, result); } [TestMethod] public async Task GetTemp_GivenAZipCode_UsesThatZipCode() { - // Arrange - var (apiuxClient, mockHttp) = Factory(); - const int zipCode = 57105; - const long fakeTemp = (long)70.5; - var response = new WeatherMainResponse - { - Current = new WeatherCurrentResponse - { - Temperature = fakeTemp - } - }; - - var requestUri = $"http://api.weatherstack.com/current?access_key={WeatherStackClient.apiKey}&type=zipcode&units=f&query={zipCode}"; - var request = mockHttp.When(requestUri) - .Respond("application/json", Serialize.ToJson(response)); - - // Act - await apiuxClient.GetCurrentTempAsync(zipCode); - - // Assert - Assert.AreEqual(1, mockHttp.GetMatchCount(request)); + + Assert.Fail(); + + //// Arrange + //var (weatherStackClient, mockHttp) = Factory(); + //const int zipCode = 57105; + //const long fakeTemp = (long)70.5; + //var response = new WeatherMainResponse + //{ + // Current = new WeatherCurrentResponse + // { + // Temperature = fakeTemp + // } + //}; + + //var requestUri = $"http://api.weatherstack.com/current?access_key={WeatherStackClient.apiKey}&type=zipcode&units=f&query={zipCode}"; + //var request = mockHttp.When(requestUri) + // .Respond("application/json", Serialize.ToJson(response)); + + //// Act + //await weatherStackClient.GetCurrentTempAsync(zipCode); + + //// Assert + //Assert.AreEqual(1, mockHttp.GetMatchCount(request)); } [TestMethod] public async Task GetTemp_GivenAZipCode_NotFound_ThrowsException() { - // how do we handle error states outside our control? - // Arrange - var (apiuxClient, mockHttp) = Factory(); - const int zipCode = 57105; - const long fakeTemp = (long)70.5; - var response = new WeatherMainResponse - { - Current = new WeatherCurrentResponse - { - Temperature = fakeTemp - } - }; - var requestUri = $"http://api.weatherstack.com/current?access_key={WeatherStackClient.apiKey}&type=zipcode&units=f&query={zipCode}"; - - var request = mockHttp.When(requestUri) - .Respond(HttpStatusCode.NotFound); - - // Act and Assert - await Assert.ThrowsExceptionAsync(async () => - { - await apiuxClient.GetCurrentTempAsync(zipCode); - }); - } + Assert.Fail(); + // // how do we handle error states outside our control? + // // Arrange + // var (weatherStackClient, mockHttp) = Factory(); + // const int zipCode = 57105; + // const long fakeTemp = (long)70.5; + // var response = new WeatherMainResponse + // { + // Current = new WeatherCurrentResponse + // { + // Temperature = fakeTemp + // } + // }; + // var requestUri = $"http://api.weatherstack.com/current?access_key={WeatherStackClient.apiKey}&type=zipcode&units=f&query={zipCode}"; + + // var request = mockHttp.When(requestUri) + // .Respond(HttpStatusCode.NotFound); - // TOD add tests for historical - } + // // Act and Assert + // await Assert.ThrowsExceptionAsync(async () => + // { + // await weatherStackClient.GetCurrentTempAsync(zipCode); + // }); + //} + + + // TOD add tests for historical + } }