Add WireMock.Net Example

This commit is contained in:
Alex Hyett 2021-05-21 14:26:22 +01:00
commit 905ed076d2
22 changed files with 653 additions and 0 deletions

37
.gitignore vendored Normal file
View file

@ -0,0 +1,37 @@
*.swp
*.*~
project.lock.json
.DS_Store
*.pyc
nupkg/
# Visual Studio Code
.vscode
# Rider
.idea
# User-specific files
*.suo
*.user
*.userosscache
*.sln.docstates
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
build/
bld/
[Bb]in/
[Oo]bj/
[Oo]ut/
msbuild.log
msbuild.err
msbuild.wrn
# Visual Studio 2015
.vs/

56
WireMock.Net.Example.sln Normal file
View file

@ -0,0 +1,56 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26124.0
MinimumVisualStudioVersion = 15.0.26124.0
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{14CA195A-D0F4-4CAC-A835-40ADB445BF22}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WireMock.Net.Api", "src\WireMock.Net.Api\WireMock.Net.Api.csproj", "{699E6556-7E60-4700-800B-801459A7F39C}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{A5937CA8-8628-4459-BA3A-4FA546656CBE}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WireMock.Net.Test", "test\WireMock.Net.Test\WireMock.Net.Test.csproj", "{BADCBF90-C083-4579-87AC-95CD58D02FE7}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{699E6556-7E60-4700-800B-801459A7F39C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{699E6556-7E60-4700-800B-801459A7F39C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{699E6556-7E60-4700-800B-801459A7F39C}.Debug|x64.ActiveCfg = Debug|Any CPU
{699E6556-7E60-4700-800B-801459A7F39C}.Debug|x64.Build.0 = Debug|Any CPU
{699E6556-7E60-4700-800B-801459A7F39C}.Debug|x86.ActiveCfg = Debug|Any CPU
{699E6556-7E60-4700-800B-801459A7F39C}.Debug|x86.Build.0 = Debug|Any CPU
{699E6556-7E60-4700-800B-801459A7F39C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{699E6556-7E60-4700-800B-801459A7F39C}.Release|Any CPU.Build.0 = Release|Any CPU
{699E6556-7E60-4700-800B-801459A7F39C}.Release|x64.ActiveCfg = Release|Any CPU
{699E6556-7E60-4700-800B-801459A7F39C}.Release|x64.Build.0 = Release|Any CPU
{699E6556-7E60-4700-800B-801459A7F39C}.Release|x86.ActiveCfg = Release|Any CPU
{699E6556-7E60-4700-800B-801459A7F39C}.Release|x86.Build.0 = Release|Any CPU
{BADCBF90-C083-4579-87AC-95CD58D02FE7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BADCBF90-C083-4579-87AC-95CD58D02FE7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BADCBF90-C083-4579-87AC-95CD58D02FE7}.Debug|x64.ActiveCfg = Debug|Any CPU
{BADCBF90-C083-4579-87AC-95CD58D02FE7}.Debug|x64.Build.0 = Debug|Any CPU
{BADCBF90-C083-4579-87AC-95CD58D02FE7}.Debug|x86.ActiveCfg = Debug|Any CPU
{BADCBF90-C083-4579-87AC-95CD58D02FE7}.Debug|x86.Build.0 = Debug|Any CPU
{BADCBF90-C083-4579-87AC-95CD58D02FE7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BADCBF90-C083-4579-87AC-95CD58D02FE7}.Release|Any CPU.Build.0 = Release|Any CPU
{BADCBF90-C083-4579-87AC-95CD58D02FE7}.Release|x64.ActiveCfg = Release|Any CPU
{BADCBF90-C083-4579-87AC-95CD58D02FE7}.Release|x64.Build.0 = Release|Any CPU
{BADCBF90-C083-4579-87AC-95CD58D02FE7}.Release|x86.ActiveCfg = Release|Any CPU
{BADCBF90-C083-4579-87AC-95CD58D02FE7}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{699E6556-7E60-4700-800B-801459A7F39C} = {14CA195A-D0F4-4CAC-A835-40ADB445BF22}
{BADCBF90-C083-4579-87AC-95CD58D02FE7} = {A5937CA8-8628-4459-BA3A-4FA546656CBE}
EndGlobalSection
EndGlobal

View file

@ -0,0 +1,15 @@
using System.Threading;
using System.Threading.Tasks;
using Refit;
using WireMock.Net.Api.Client.Model;
namespace WireMock.Net.Api.Client
{
public interface IWeatherClient
{
[Get("/bin/civillight.php?lat={latitude}&lon={longitude}&ac=0&unit=metric&output=json&tzshift=0")]
Task<ApiResponse<WeatherResponse>> GetWeatherAsync(decimal latitude, decimal longitude, CancellationToken ct);
}
}

View file

@ -0,0 +1,39 @@
using System.Collections.Generic;
using Newtonsoft.Json;
namespace WireMock.Net.Api.Client.Model
{
public class WeatherResponse
{
[JsonProperty("product")]
public string Product { get; set; }
[JsonProperty("init")]
public string Init { get; set; }
[JsonProperty("dataseries")]
public IEnumerable<WeatherData> Dataseries { get; set; }
}
public class WeatherData
{
[JsonProperty("date")]
public int Date { get; set; }
[JsonProperty("weather")]
public string Weather { get; set; }
[JsonProperty("temp2m")]
public Temperature Temp2m { get; set; }
}
public class Temperature
{
[JsonProperty("max")]
public int Max { get; set; }
[JsonProperty("min")]
public int Min { get; set; }
}
}

View file

@ -0,0 +1,49 @@
using System;
using System.Globalization;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using WireMock.Net.Api.Client;
namespace WireMock.Net.Api.Controllers
{
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
private readonly ILogger<WeatherForecastController> _logger;
private readonly IWeatherClient _weatherClient;
public WeatherForecastController(ILogger<WeatherForecastController> logger, IWeatherClient weatherClient)
{
_logger = logger ??
throw new ArgumentNullException(nameof(logger));
_weatherClient = weatherClient ??
throw new ArgumentNullException(nameof(weatherClient));
}
[HttpGet]
public async Task<IActionResult> GetAsync(CancellationToken ct)
{
var weatherResponse = await _weatherClient.GetWeatherAsync(51.5074m, 0.1278m, ct);
if (!weatherResponse.IsSuccessStatusCode || weatherResponse?.Content?.Dataseries == null)
{
_logger.LogError("Unexpected status code from Weather API {StatusCode}", weatherResponse.StatusCode);
return new StatusCodeResult(StatusCodes.Status500InternalServerError);
}
return Ok(weatherResponse.Content.Dataseries.Select(weather => new WeatherForecast
{
Date = DateTime.ParseExact(weather.Date.ToString(), "yyyyMMdd", CultureInfo.InvariantCulture),
TemperatureC = new Temperature { Min = weather.Temp2m.Min, Max = weather.Temp2m.Max },
Summary = weather.Weather
}));
}
}
}

View file

@ -0,0 +1,8 @@
namespace WireMock.Net.Api.Configuration
{
public class WeatherSettings
{
public string BaseAddress { get; set; }
public int TimeoutSeconds { get; set; }
}
}

View file

@ -0,0 +1,28 @@
using System;
using System.Net.Http.Headers;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Refit;
using WireMock.Net.Api.Client;
using WireMock.Net.Api.Configuration;
namespace WireMock.Net.Api.Infrastructure.Extensions
{
public static class ServiceCollectionExtensions
{
public static IServiceCollection AddWeatherClient(this IServiceCollection services, IConfiguration configuration)
{
var settings = configuration.GetSection("WeatherClient").Get<WeatherSettings>();
services.AddRefitClient<IWeatherClient>().ConfigureHttpClient((sp, client) =>
{
client.BaseAddress = new Uri(settings.BaseAddress);
client.Timeout = TimeSpan.FromSeconds(settings.TimeoutSeconds);
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
});
return services;
}
}
}

View file

@ -0,0 +1,16 @@
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
namespace WireMock.Net.Api
{
public class Program
{
public static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args).UseStartup<Startup>();
}
}

View file

@ -0,0 +1,30 @@
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:57743",
"sslPort": 44305
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "weatherforecast",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"WireMock.Net.Api": {
"commandName": "Project",
"launchBrowser": true,
"launchUrl": "weatherforecast",
"applicationUrl": "https://localhost:5001;http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

View file

@ -0,0 +1,52 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using WireMock.Net.Api.Infrastructure.Extensions;
namespace WireMock.Net.Api
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddWeatherClient(Configuration);
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
}

View file

@ -0,0 +1,8 @@
namespace WireMock.Net.Api
{
public class Temperature
{
public int Min { get; set; }
public int Max { get; set; }
}
}

View file

@ -0,0 +1,23 @@
using System;
namespace WireMock.Net.Api
{
public class WeatherForecast
{
public DateTime Date { get; set; }
public Temperature TemperatureC { get; set; }
public Temperature TemperatureF => ConvertToF(TemperatureC);
public string Summary { get; set; }
private Temperature ConvertToF(Temperature tempC)
{
return new Temperature
{
Min = 32 + (int) (tempC.Min / 0.5556), Max = 32 + (int) (tempC.Max / 0.5556)
};
}
}
}

View file

@ -0,0 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp5.0</TargetFramework>
<RootNamespace>WireMock.Net.Api</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Refit" Version="6.0.38"/>
<PackageReference Include="Refit.HttpClientFactory" Version="6.0.38"/>
<PackageReference Include="Newtonsoft.Json" Version="13.0.1"/>
</ItemGroup>
</Project>

View file

@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
}
}

View file

@ -0,0 +1,14 @@
{
"WeatherClient": {
"BaseAddress": "https://www.7timer.info",
"TimeoutSeconds": 30
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*"
}

View file

@ -0,0 +1,28 @@
using System.IO;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.Extensions.Configuration;
using WireMock.Net.Api;
namespace WireMock.Net.Test.Infrastructure
{
public class ApiWebFactory<TStartup> : WebApplicationFactory<TStartup> where TStartup : class
{
protected override IWebHostBuilder CreateWebHostBuilder() =>
WebHost.CreateDefaultBuilder()
.ConfigureAppConfiguration((context, config) =>
{
config
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", true, true)
.AddJsonFile("appsettings.test.json", false, false)
.AddEnvironmentVariables();
})
.UseContentRoot(Directory.GetCurrentDirectory())
.UseKestrel()
.UseStartup<Startup>();
}
}

View file

@ -0,0 +1,36 @@
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Testing;
using Newtonsoft.Json;
using WireMock.Net.Api;
using Xunit;
namespace WireMock.Net.Test.Infrastructure
{
[Collection("sequential")]
public abstract class IntegrationBase : IClassFixture<ApiWebFactory<Startup>>
{
protected HttpClient HttpClient { get; }
protected IntegrationBase(WebApplicationFactory<Startup> factory)
{
HttpClient = factory.CreateClient();
factory.Server.AllowSynchronousIO = true;
}
protected HttpRequestMessage CreateGetRequest(string url)
{
return new HttpRequestMessage(HttpMethod.Get, url);
}
protected static async Task<T> ReadResponseAsync<T>(HttpResponseMessage response)
{
var result = await response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<T>(result);
}
}
}

View file

@ -0,0 +1,48 @@
using System;
using System.IO;
using WireMock.RequestBuilders;
using WireMock.ResponseBuilders;
using WireMock.Server;
namespace WireMock.Net.Test.Infrastructure
{
public class WeatherFixture : IDisposable
{
protected readonly WireMockServer _mockApi;
public WeatherFixture()
{
_mockApi = WireMockServer.Start(50000);
}
public void Dispose()
{
_mockApi.Stop();
}
public void Reset()
{
_mockApi.Reset();
}
public IRequestBuilder SetupGetWeather(string responseBodyResource, int statusCode = 200)
{
var request = Request.Create()
.UsingGet()
.WithPath("/bin/civillight.php*");
var responseBody = string.IsNullOrWhiteSpace(responseBodyResource) ? new byte[0] : File.ReadAllBytes(responseBodyResource);
_mockApi.Given(request)
.RespondWith(
Response.Create()
.WithStatusCode(statusCode)
.WithHeader("content-type", "application/json")
.WithBody(responseBody)
);
return request;
}
}
}

View file

@ -0,0 +1,66 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using FluentAssertions;
using WireMock.Net.Api;
using WireMock.Net.Test.Infrastructure;
using Xunit;
namespace WireMock.Net.Test
{
public class IntegrationTest : IntegrationBase, IDisposable
{
private readonly WeatherFixture _weatherFixture;
public IntegrationTest(ApiWebFactory<Startup> factory) : base(factory)
{
_weatherFixture = new WeatherFixture();
}
public void Dispose()
{
_weatherFixture.Reset();
_weatherFixture.Dispose();
}
[Fact]
public async Task Given_weather_api_successful_returns_weather()
{
// Arrange
_weatherFixture.SetupGetWeather("Resources/success.json");
// Act
var request = CreateGetRequest("/weatherforecast");
var result = await HttpClient.SendAsync(request);
// Assert
result.StatusCode.Should().Be(HttpStatusCode.OK);
var response = await ReadResponseAsync<IEnumerable<WeatherForecast>>(result);
response.Should().HaveCount(7);
response.Should().Contain(x =>
x.Date == DateTime.Parse("2021-05-25") &&
x.Summary == "rain" &&
x.TemperatureC.Max == 12 &&
x.TemperatureC.Min == 7);
}
[Fact]
public async Task Given_weather_api_unsuccessful_returns_500()
{
// Arrange
_weatherFixture.SetupGetWeather(null, 503);
// Act
var request = CreateGetRequest("/weatherforecast");
var result = await HttpClient.SendAsync(request);
// Assert
result.StatusCode.Should().Be(HttpStatusCode.InternalServerError);
}
}
}

View file

@ -0,0 +1,48 @@
{
"product": "civillight",
"init": "2021052100",
"dataseries": [
{
"date": 20210521,
"weather": "lightrain",
"temp2m": { "max": 11, "min": 9 },
"wind10m_max": 4
},
{
"date": 20210522,
"weather": "lightrain",
"temp2m": { "max": 10, "min": 7 },
"wind10m_max": 3
},
{
"date": 20210523,
"weather": "lightrain",
"temp2m": { "max": 12, "min": 4 },
"wind10m_max": 4
},
{
"date": 20210524,
"weather": "rain",
"temp2m": { "max": 12, "min": 7 },
"wind10m_max": 3
},
{
"date": 20210525,
"weather": "rain",
"temp2m": { "max": 12, "min": 7 },
"wind10m_max": 3
},
{
"date": 20210526,
"weather": "rain",
"temp2m": { "max": 15, "min": 6 },
"wind10m_max": 3
},
{
"date": 20210527,
"weather": "clear",
"temp2m": { "max": 18, "min": 6 },
"wind10m_max": 3
}
]
}

View file

@ -0,0 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp5.0</TargetFramework>
<RootNamespace>WireMock.Net.Test</RootNamespace>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0"/>
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="5.0.6"/>
<PackageReference Include="xunit" Version="2.4.0"/>
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.0"/>
<PackageReference Include="coverlet.collector" Version="1.2.0"/>
<PackageReference Include="WireMock.Net" Version="1.4.15"/>
<PackageReference Include="FluentAssertions" Version="5.10.3"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\WireMock.Net.Api\WireMock.Net.Api.csproj"/>
</ItemGroup>
<ItemGroup>
<None Update="appsettings.test.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<Content Include="Resources\*.*">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
</Project>

View file

@ -0,0 +1,5 @@
{
"WeatherClient": {
"BaseAddress": "http://localhost:50000"
}
}