-
-
Notifications
You must be signed in to change notification settings - Fork 209
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Is it possible to use WireMock as a middleware? #1035
Comments
@adrianiftode I guess you are using a modern .NET version like 6 or higher? There is already an internal class named
Maybe that can be used? |
Yes, but the reason for asking this is I would like to deploy it with the tested service. I have a service ServiceB that I would like to mock. I want to configure WireMock inside ServiceA. So when ServiceA is started, the Mocked ServiceB is also started and ready to accept requests. The Mock should listen to any HTTP request to a path that starts with I will give it a try to the WireMockMiddleware. To expand this, I would also like to deploy a WireMock image configured with different mocked services
This image is then deployed and accessible from the Sandbox/Tests servers, and it has some kind of "mock isolation". So each path prefix has its own WireMock configuration. |
A quick question: Why not deploy 1 (or multiple) docker container(s) of WireMock.Net ? |
Even deploying it within a separated container, I was hoping to be a single one. I think I will go with this route anyway (multiple containers, one per every mocked service) |
Or use one container and register the mappings with a prefix in the path? |
Why not just use wiremock as a docker container using docker compose, see https://github.com/matteus6007/MyDomain.Api.Template/blob/main/docker-compose.dev-env.yml#L58 as an example of setting this up, then add your mocks in the normal JSON format into the |
I actually built this as a combination of a .NET HostedService (for Wiremock Server) and a DelegatingHandler which checks the original request headers for something like "X-WireMockStatus" and then rerouted all HttpClient calls to WireMockServer. This worked well to allow me to run this WireMockServer in all our lower environments for testing purposes. |
@matteus6007 |
@StefH Let me see what I can do about sharing this with you all. |
I'm very interested in that question as well. I'm working in enviroment where every service must have a bunch of specific middlewares, i can't avoid it. So i have to add that middlewares to wiremock or wiremock middleware to my service. So if this is possible please provide some hint in either direction |
So here's a snapshot of how the implementation is performed: WiremockServerInstance - This is the class that will be used by the background service. /// <summary>
/// WireMockServer Instance object
/// </summary>
public class WireMockServerInstance
{
private readonly Action<WireMockServer> _configureAction;
private readonly WireMockServerSettings _settings;
#region Constructors
/// <summary>
/// Creates a new instance and provides ability to add configuration
/// to the <see cref="WireMockServer"/>
/// </summary>
/// <param name="configure"></param>
public WireMockServerInstance(Action<WireMockServer> configure)
: this(configure, null) { }
/// <summary>
/// Creates a new instance and provides ability to add configuration
/// for the start method of <see cref="WireMockServer"/>
/// </summary>
/// <param name="configure"></param>
/// <param name="settings"></param>
public WireMockServerInstance(Action<WireMockServer> configure, WireMockServerSettings settings)
{
_configureAction = configure;
_settings = settings;
}
#endregion
#region Properties
/// <summary>
/// Instance accessor for the <see cref="WireMockServer" />
/// </summary>
public WireMockServer Instance { get; private set; }
/// <summary>
/// Retrieves the URI for the <see cref="WireMockServer"/>
/// </summary>
/// <returns></returns>
/// <exception cref="Exception"></exception>
public string GetInstanceUri() => Instance.Urls.FirstOrDefault() ?? throw new Exception("No URL found for WireMockServer");
#endregion
#region Methods
/// <summary>
/// Configures and starts <see cref="WireMockServer"/> instance for use.
/// </summary>
public void Start()
{
Instance = (_settings != null)
? WireMockServer.Start(_settings)
: WireMockServer.Start();
_configureAction.Invoke(Instance);
}
#endregion
/// <summary>
/// Stops the <see cref="WireMockServer"/>
/// </summary>
public void Stop()
{
if (Instance != null && (Instance.IsStarted || Instance.IsStartedWithAdminInterface))
Instance.Stop();
}
} WiremockContext - Context to allow me to control certain functionality of the Wiremock instance /// <summary>
/// Wiremock context
/// </summary>
public class WiremockContext : IWiremockContext
{
/// <summary>
/// Is Wiremock enabled?
/// </summary>
public bool? IsEnabled { get; set; }
/// <summary>
/// Duration to delay the response in milliseconds
/// </summary>
public int ResponseDelayInMs { get; set; }
} WireMockDelegationHandler - DelegatingHandler class allowing us to tap into the HttpClient object and perform our magic redirects to Wiremock without having to change our code. THIS is where the magic happens /// <summary>
/// DelegatingHandler that takes requests made via the <see cref="HttpClient"/>
/// and routes them to the <see cref="WireMockServer"/>
/// </summary>
public class WireMockDelegationHandler : DelegatingHandler
{
private readonly WireMockServerInstance _server;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly ILogger<WireMockDelegationHandler> _logger;
/// <summary>
/// Creates a new instance of <see cref="WireMockDelegationHandler"/>
/// </summary>
/// <param name="server"></param>
/// <param name="httpContextAccessor"></param>
/// <param name="logger"></param>
/// <exception cref="ArgumentNullException"></exception>
public WireMockDelegationHandler(WireMockServerInstance server, IHttpContextAccessor httpContextAccessor, ILogger<WireMockDelegationHandler> logger)
{
_server = server ?? throw new ArgumentNullException(nameof(server));
_httpContextAccessor = httpContextAccessor ?? throw new ArgumentNullException(nameof(httpContextAccessor));
_logger = logger;
}
/// <inheritdoc />
/// <exception cref="ArgumentNullException"></exception>
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
if (request is null)
throw new ArgumentNullException(nameof(request));
if (_httpContextAccessor.HttpContext is null)
throw new ArgumentNullException(nameof(_httpContextAccessor.HttpContext));
bool shouldRedirectToWireMock = IsWireMockStatusHeaderSet();
var (shouldDelayResponse, delayInMs) = IsDelayHeaderSet();
if (shouldRedirectToWireMock)
{
_logger?.LogDebug("Redirecting request to WireMock server");
if (_server.Instance is not null
&& _server.Instance.Urls is not null
&& _server.Instance.Urls.Any())
request.RequestUri = new Uri(_server.GetInstanceUri() + request.RequestUri.PathAndQuery);
}
if (shouldDelayResponse)
await Task.Delay(delayInMs);
return await base.SendAsync(request, cancellationToken);
}
private bool IsWireMockStatusHeaderSet()
{
bool shouldRedirectToWireMock = false;
if (_httpContextAccessor.HttpContext.Request.Headers.ContainsKey(AppConstants.HEADER_WIREMOCK_STATUS))
{
_logger?.LogDebug("Found WireMock header on request");
if (_httpContextAccessor.HttpContext.Request.Headers[AppConstants.HEADER_WIREMOCK_STATUS].ToString().Equals("true", StringComparison.OrdinalIgnoreCase))
shouldRedirectToWireMock = true;
}
return shouldRedirectToWireMock;
}
private (bool, int) IsDelayHeaderSet()
{
bool shouldDelayResponse = false;
int delayInMs = 0;
if (_httpContextAccessor.HttpContext.Request.Headers.ContainsKey(AppConstants.HEADER_RESPONSE_DELAY))
{
string delay = _httpContextAccessor.HttpContext.Request.Headers[AppConstants.HEADER_RESPONSE_DELAY].ToString();
if (!int.TryParse(delay, out delayInMs))
throw new ArgumentOutOfRangeException(nameof(delay), "Delay must be an integer");
_logger?.LogDebug("Delaying response by {0}ms", delayInMs);
shouldDelayResponse = true;
}
return (shouldDelayResponse, delayInMs);
}
} WireMockBgService - This is the background service that will hold onto our instance of Wiremock and allow us to keep from spinning up new copies with every request. /// <summary>
/// <see cref="BackgroundService"/> used to start/stop the <see cref="WireMockServer"/>
/// </summary>
public class WireMockBgService : BackgroundService
{
private readonly WireMockServerInstance _server;
/// <summary>
/// Creates a new <see cref="BackgroundService"/> using an instance
/// of <see cref="WireMockServerInstance"/>
/// </summary>
/// <param name="server"></param>
public WireMockBgService(WireMockServerInstance server)
{
_server = server ?? throw new ArgumentNullException(nameof(server));
}
/// <inheritdoc />
protected override Task ExecuteAsync(CancellationToken stoppingToken)
{
_server.Start();
return Task.CompletedTask;
}
/// <inheritdoc />
public override Task StopAsync(CancellationToken cancellationToken)
{
_server.Stop();
return base.StopAsync(cancellationToken);
}
} ServiceCollectionExtensions - Extension methods to make it easy for integrating into any app. /// <summary>
/// Extension methods for <see cref="IServiceCollection"/>.
/// </summary>
public static class ServiceCollectionExtensions
{
/// <summary>
/// Adds all the components necessary to run Wiremock.NET in the background.
/// </summary>
/// <param name="services"></param>
/// <param name="server"></param>
/// <returns></returns>
public static IServiceCollection AddWireMockService(this IServiceCollection services, Action<WireMockServer> server)
{
return services.AddWireMockService(server, null);
}
/// <summary>
/// Adds all the components necessary to run Wiremock.NET in the background.
/// </summary>
/// <param name="services"></param>
/// <param name="configure"></param>
/// <param name="settings"></param>
/// <returns></returns>
public static IServiceCollection AddWireMockService(this IServiceCollection services, Action<WireMockServer> configure, WireMockServerSettings settings)
{
services.AddTransient<WireMockDelegationHandler>();
if (settings is null)
services.AddSingleton(new WireMockServerInstance(configure));
else
services.AddSingleton(new WireMockServerInstance(configure, settings));
services.AddHostedService<WireMockBgService>();
services.AddHttpClient();
services.AddHttpContextAccessor();
services.ConfigureAll<HttpClientFactoryOptions>(options =>
{
options.HttpMessageHandlerBuilderActions.Add(builder =>
{
builder.AdditionalHandlers.Add(builder.Services.GetRequiredService<WireMockDelegationHandler>());
});
});
return services;
}
} Now for it's usage! Using a minimal API, we can have something like this: var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
if (!builder.Environment.IsProduction())
{
builder.Services.AddWireMockService(server =>
{
server.Given(Request.Create()
.WithPath("<your path that you want to mock>")
.UsingAnyMethod()
).RespondWith(Response.Create()
.WithStatusCode(200)
.WithBody("<your body to respond with when it's called>");
});
}
var app = builder.Build();
// Configure the HTTP request pipeline.
app.UseAuthorization();
app.MapControllers();
app.Run(); Hopefully this helps some of y'all develop the pattern to make this a thing! |
Thank you very much for the detailed example |
@matthewyost |
@StefH The stuff between the < > is just supposed to be whatever path you want to use. It's meant to be replaced with whatever path you're attempting to mock a response for. |
@Act0r did you get it working? |
@StefH Actually i posponed the idea and decide to implement requested features by myself. |
@Act0r I got it working. I was thinking in the wrong direction... This solution actually translates any calls made from a WebApplication to another API (e.g. Example: app.MapGet("/weatherforecast", async (HttpClient client) =>
{
// ⭐ This injected HttpClient will not call the real api, but will call WireMock.Net !
var result = await client.GetStringAsync("https://real-api:12345/test1");
return Enumerable.Range(1, 3).Select(index =>
new WeatherForecast
(
DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
Random.Shared.Next(-20, 55),
result
));
}); I'm not sure that is the same idea as @adrianiftode had? My thoughts were initially that this WireMock.Net instance would be handling additional calls in a WebApplication.
|
PR is merged. |
Currently I see that I can use wiremock hosted in different forms, however what I would like to do is to have a path prefix and everything after that path to be handled by wiremock.
So something like
I would like to deploy an arbitrary .NET Service, but still use wiremock at the port 80 and let it handle some prefixed requests.
The text was updated successfully, but these errors were encountered: