Set dummy IP address in integration test with Asp.Net Core TestServer - asp.net-core

I have a C# Asp.Net Core (1.x) project, implementing a web REST API, and its related integration test project, where before any test there's a setup similar to:
// ...
IWebHostBuilder webHostBuilder = GetWebHostBuilderSimilarToRealOne()
.UseStartup<MyTestStartup>();
TestServer server = new TestServer(webHostBuilder);
server.BaseAddress = new Uri("http://localhost:5000");
HttpClient client = server.CreateClient();
// ...
During tests, the client is used to send HTTP requests to web API (the system under test) and retrieve responses.
Within actual system under test there's some component extracting sender IP address from each request, as in:
HttpContext httpContext = ReceiveHttpContextDuringAuthentication();
// edge cases omitted for brevity
string remoteIpAddress = httpContext?.Connection?.RemoteIpAddress?.ToString()
Now during integration tests this bit of code fails to find an IP address, as RemoteIpAddress is always null.
Is there a way to set that to some known value from within test code? I searched here on SO but could not find anything similar. TA

You can write middleware to set custom IP Address since this property is writable:
public class FakeRemoteIpAddressMiddleware
{
private readonly RequestDelegate next;
private readonly IPAddress fakeIpAddress = IPAddress.Parse("127.168.1.32");
public FakeRemoteIpAddressMiddleware(RequestDelegate next)
{
this.next = next;
}
public async Task Invoke(HttpContext httpContext)
{
httpContext.Connection.RemoteIpAddress = fakeIpAddress;
await this.next(httpContext);
}
}
Then you can create StartupStub class like this:
public class StartupStub : Startup
{
public StartupStub(IConfiguration configuration) : base(configuration)
{
}
public override void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseMiddleware<FakeRemoteIpAddressMiddleware>();
base.Configure(app, env);
}
}
And use it to create a TestServer:
new TestServer(new WebHostBuilder().UseStartup<StartupStub>());

As per this answer in ASP.NET Core, is there any way to set up middleware from Program.cs?
It's also possible to configure the middleware from ConfigureServices, which allows you to create a custom WebApplicationFactory without the need for a StartupStub class:
public class CustomWebApplicationFactory : WebApplicationFactory<Startup>
{
protected override IWebHostBuilder CreateWebHostBuilder()
{
return WebHost
.CreateDefaultBuilder<Startup>(new string[0])
.ConfigureServices(services =>
{
services.AddSingleton<IStartupFilter, CustomStartupFilter>();
});
}
}
public class CustomStartupFilter : IStartupFilter
{
public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next)
{
return app =>
{
app.UseMiddleware<FakeRemoteIpAddressMiddleware>();
next(app);
};
}
}

Using WebHost.CreateDefaultBuilder can mess up with your app configuration.
And there's no need to change Product code just to accommodate for testing, unless absolutely necessary.
The simplest way to add your own middleware, without overriding Startup class methods, is to add the middleware through a IStartupFilterā€ as suggested by Elliott's answer.
But instead of using WebHost.CreateDefaultBuilder, just use
base.CreateWebHostBuilder().ConfigureServices...
public class CustomWAF : WebApplicationFactory<Startup>
{
protected override IWebHostBuilder CreateWebHostBuilder()
{
return base.CreateWebHostBuilder().ConfigureServices(services =>
{
services.AddSingleton<IStartupFilter, CustomStartupFilter>();
});
}
}

I used Elliott's answer within an ASP.NET Core 2.2 project. However, updating to ASP.NET 5.0, I had to replace the override of CreateWebHostBuilder with the below override of CreateHostBuilder:
protected override IHostBuilder CreateHostBuilder()
{
return Host
.CreateDefaultBuilder()
.ConfigureWebHostDefaults(builder =>
{
builder.UseStartup<Startup>();
})
.ConfigureServices(services =>
{
services.AddSingleton<IStartupFilter, CustomStartupFilter>();
});
}

Related

.NET Core Middleware - access IApplicationBuilder in a controller?

I need to access IApplicationBuilder inside a controller.
What I have tried :-
I have written middleware (app.UseMyMiddleware) as follows
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, IHttpContextAccessor httpContextAccessor)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseMyMiddleware();
app.UseAuthentication();
app.UseSession();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
public class MyMiddleware
{
private readonly RequestDelegate _next;
public MyMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
///TODO - Pass IApplicationBuilder to HttpContext
await _next(context);
}
}
public static class MiddlewareExtensions
{
public static IApplicationBuilder UseMyMiddleware(this IApplicationBuilder builder)
{
return builder.UseMiddleware<MyMiddleware>();
}
}
but I can't figure out how I can pass IApplicationBuilder to HttpContext in Invoke method. so, that I can use it in a controller.
I have also referred following stackoverflow question-answer
how to access IApplicationBuilder in a controller?
.Net Core Middleware - Getting Form Data from Request
Question(s) :-
How can pass IApplicationBuilder to HttpContext in Invoke method to use it in controller?
Is there any better way to access IApplicationBuilder inside controller apart from middleware?
IApplicationBuilder was not designed to work the way you want it to. Instead, if you have some data created at build time that you want to be available to middleware add a Singleton to the services and inject the singleton into the middleware.
You cannot access IApplicationBuilder anywhere later after completing the application building phase (after running Configure method). It's not available for injection at all.
However for the purpose of plugging-in or configuring middlewares at runtime based on request data (from HttpContext), you can use .UseWhen. Another one for terminal middleware is .MapWhen but I think that's not for your case. Here is an example of .UseWhen:
public static class MiddlewareExtensions
{
public static IApplicationBuilder UseMyMiddleware(this IApplicationBuilder builder)
{
var allOptions = new [] {"option 1","option 2"};
foreach(var option in allOptions){
var currentOption = option;
builder.UseWhen(context => {
//suppose you can get the user's selected option from query string
var selectedOption = context.Request.Query["option_key"];
return selectedOption == currentOption;
}, app => {
//your MyMiddleware is supposed to accept one argument
app.UseMiddleware<MyMiddleware>(currentOption);
});
}
return builder;
}
}
To simplify it I suppose your options are just strings, you must know beforehand all possible options that the user can select via UI. Each one will be an exact match for the condition to plug-in a middleware and they must be all exclusive (so just one of them can enable one corresponding middleware), otherwise there will be duplicate middlewares, which may cause some issue.
By expressing the foreach above more clearly, it may represent something as follows:
//kind of pseudo code
if(selectedOption1){
app.UseMiddleware<MyMiddleware>("option 1");
} else if(selectedOption2){
app.UseMiddleware<MyMiddleware>("option 2");
}
...
You must decide how you get the selected option from the user (in the example above I get it from query string). You can get it from Cookie as well (to remember the user's selection) or from other sources such as route data, headers, form, request body. I think that's another issue, so if you have problem with that, please ask in another question.
First up all thanks to #Kingking and #GlennSills for there solution and valuable comments.
I have solved this problem as
Created one class which inherit from Hangfire.JobStorage as follows
public class HangfireSqlServerStorageExtension : Hangfire.JobStorage
{
private readonly HangfireSqlServerStorage _hangfireSqlServerStorage = new HangfireSqlServerStorage();
public HangfireSqlServerStorageExtension(string nameOrConnectionString)
{
_hangfireSqlServerStorage.SqlServerStorageOptions = new SqlServerStorageOptions();
_hangfireSqlServerStorage.SqlServerStorage = new SqlServerStorage(nameOrConnectionString, _hangfireSqlServerStorage.SqlServerStorageOptions);
}
public HangfireSqlServerStorageExtension(string nameOrConnectionString, SqlServerStorageOptions options)
{
_hangfireSqlServerStorage.SqlServerStorageOptions = options;
_hangfireSqlServerStorage.SqlServerStorage = new SqlServerStorage(nameOrConnectionString, _hangfireSqlServerStorage.SqlServerStorageOptions);
}
public void UpdateConnectionString(string nameOrConnectionString)
{
_hangfireSqlServerStorage.SqlServerStorage = new SqlServerStorage(nameOrConnectionString, _hangfireSqlServerStorage.SqlServerStorageOptions);
}
public override IStorageConnection GetConnection()
{
return _hangfireSqlServerStorage.SqlServerStorage.GetConnection();
}
public override IMonitoringApi GetMonitoringApi()
{
return _hangfireSqlServerStorage.SqlServerStorage.GetMonitoringApi();
}
}
HangfireSqlServerStorage.cs
Used in HangfireSqlServerStorageExtension class above
public class HangfireSqlServerStorage
{
public SqlServerStorage SqlServerStorage { get; set; }
public SqlServerStorageOptions SqlServerStorageOptions { get; set; }
}
Startup.cs
In Startup file add singleton service for HangfireSqlServerStorageExtension instance and configure hangfire dashboard as follows
public class Startup
{
///Other necessary code here
public static HangfireSqlServerStorageExtension HangfireSqlServerStorageExtension { get; private set; }
public void ConfigureServices(IServiceCollection services)
{
///Other necessary code here
HangfireSqlServerStorageExtension = new HangfireSqlServerStorageExtension("DBConnecttionString"));
services.AddSingleton<HangfireSqlServerStorageExtension>(HangfireSqlServerStorageExtension);
services.AddHangfire(configuration => configuration.SetDataCompatibilityLevel(CompatibilityLevel.Version_170));
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, IHttpContextAccessor httpContextAccessor)
{
//Other necessary code here
app.UseHangfireDashboard("/Dashboard", new DashboardOptions(), HangfireSqlServerStorageExtension);
//Other necessary code here
}
}
Inside controller I have used it as follows
HangfireController.cs
public class HangfireController : Controller
{
protected readonly HangfireSqlServerStorageExtension
hangfireSqlServerStorageExtension;
public HangfireController(HangfireSqlServerStorageExtension hangfireSqlServerStorageExtension)
{
this.hangfireSqlServerStorageExtension = hangfireSqlServerStorageExtension;
}
public IActionResult DisplayHangfireDashboard()
{
// Update connString as follows
hangfireSqlServerStorageExtension.UpdateConnectionString(connString);
var hangfireDashboardUrl = $"{this.Request.Scheme}://{this.Request.Host}{this.Request.PathBase}" + "/Dashboard";
return Json(new { url = hangfireDashboardUrl });
}
}

ASP.NET Core SignalR Adding service to hub breaks

I'm currently working on an ASP.NET Core Web Application.
I have a MQTT Server, which is connected to a service (IHostedService) and this service references a SignalR Hub.
So if there is a new message comming from the MQTT Server, it is forwarded to the hub and therefore to the client.
This works fine. But now I would like to add a button to send MQTT messages back to the MQTT server.
To do so, I added a function in the hub, which es called by the button via SignalR.
So far so good but when adding the service now to the constructor of the hub it fails, when I open the web app (not during startup), with the following message:
fail: Microsoft.AspNetCore.SignalR.HubConnectionHandler[1]
Error when dispatching 'OnConnectedAsync' on hub.
System.InvalidOperationException: Unable to resolve service for type 'websiteApp.HostedServices.UserPromptService' while attempting to activate 'websiteApp.Hubs.UserPromptHub'.
at Microsoft.Extensions.DependencyInjection.ActivatorUtilities.GetService(IServiceProvider sp, Type type, Type requiredBy, Boolean isDefaultParameterRequired)
at lambda_method(Closure , IServiceProvider , Object[] )
at Microsoft.AspNetCore.SignalR.Internal.DefaultHubActivator'1.Create()
at Microsoft.AspNetCore.SignalR.Internal.DefaultHubDispatcher'1.OnConnectedAsync(HubConnectionContext connection)
at Microsoft.AspNetCore.SignalR.Internal.DefaultHubDispatcher'1.OnConnectedAsync(HubConnectionContext connection)
at Microsoft.AspNetCore.SignalR.HubConnectionHandler'1.RunHubAsync(HubConnectionContext connection)
The service declaration looks like this:
public class UserPromptService : IHostedService, IDisposable
{
public UserPromptService(ILogger<UserPromptService> logger, IConfiguration config, UserPromptContext userPromptContext, IHubContext<UserPromptHub> userPromptHub)
{
}
}
And my hub looks like this:
public class UserPromptHub : Hub<IUserPromptHub>
{
public UserPromptHub(UserPromptService service) // everything works until I add the service here
{
service.ToString(); // just for testing
}
}
And they are configured in the Startup.cs:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// ...
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
endpoints.MapHub<Hubs.UserPromptHub>("/userPromptHub");
});
}
As well as in the Program.cs:
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
// ...
.ConfigureServices(services =>
{
services.AddSingleton<websiteApp.DataContext.UserPromptContext>();
services.AddHostedService<HostedServices.UserPromptService>();
});
Could you please help me to fix the problem?
One option to solve your problem would be to restructure your code a little bit.So instead of your UserPromptService be responsable for the MQTT connection you create a seperate class for that.
The following is only sudo code
You could create a new class
public class MQTTConnection
{
private readonly _yourMQTTServerConnection;
public MQTTConnection()
{
_yourMQTTServerConnection = new ServerConnection(connectionstring etc);
}
public Task<Message> GetMessage()
{
return _yourMQTTServerConnection.GetMessageAsync();
}
public Task SendMessage(Message message)
{
return _yourMQTTServerConnection.SendMessageAsync(message);
}
}
So your Hub look something like this
public class UserPromptHub : Hub<IUserPromptHub>
{
private readonly MQTTConnection _connection;
public UserPromptHub(MQTTConnection connection)
{
_connection = connection
}
public async Task MessageYouReceiveFromTheUser(object object)
{
// your business logic
await _connection.SendMessage(message);
}
public Task MessageYouSendToTheClient(object object)
{
await Clients.All.MessageYouSendToTheClient(message);
}
}
And your UserPromptService looks somehting like that
public class UserPromptService : IHostedService, IDisposable
{
public UserPromptService(ILogger<UserPromptService> logger,
IConfiguration config,
UserPromptContext userPromptContext,
IHubContext<UserPromptHub> userPromptHub,
MQTTConnection connection)
{
// map arguments to private fields
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while(yourAppIsUpAndRunning)
{
var message = await _connection.GetMessage()
// process the message
await _hub.MessageYouSendToTheClient(yourMessage)
}
}
}
I hope my approach is understandable, if not I can add more details.

ASP.NET Core Integration Testing & Mocking using Moq

I have the following ASP.NET Core integration test using a custom WebApplicationFactory
public class CustomWebApplicationFactory<TEntryPoint> : WebApplicationFactory<TEntryPoint>
where TEntryPoint : class
{
public CustomWebApplicationFactory()
{
this.ClientOptions.AllowAutoRedirect = false;
this.ClientOptions.BaseAddress = new Uri("https://localhost");
}
public ApplicationOptions ApplicationOptions { get; private set; }
public Mock<IClockService> ClockServiceMock { get; private set; }
public void VerifyAllMocks() => Mock.VerifyAll(this.ClockServiceMock);
protected override TestServer CreateServer(IWebHostBuilder builder)
{
this.ClockServiceMock = new Mock<IClockService>(MockBehavior.Strict);
builder
.UseEnvironment("Testing")
.ConfigureTestServices(
services =>
{
services.AddSingleton(this.ClockServiceMock.Object);
});
var testServer = base.CreateServer(builder);
using (var serviceScope = testServer.Host.Services.CreateScope())
{
var serviceProvider = serviceScope.ServiceProvider;
this.ApplicationOptions = serviceProvider.GetRequiredService<IOptions<ApplicationOptions>>().Value;
}
return testServer;
}
}
which looks like it should work but the problem is that the ConfigureTestServices method is never being called, so my mock is never registered with the IoC container. You can find the full source code here.
public class FooControllerTest : IClassFixture<CustomWebApplicationFactory<Startup>>, IDisposable
{
private readonly HttpClient client;
private readonly CustomWebApplicationFactory<Startup> factory;
private readonly Mock<IClockService> clockServiceMock;
public FooControllerTest(CustomWebApplicationFactory<Startup> factory)
{
this.factory = factory;
this.client = factory.CreateClient();
this.clockServiceMock = this.factory.ClockServiceMock;
}
[Fact]
public async Task Delete_FooFound_Returns204NoContent()
{
this.clockServiceMock.SetupGet(x => x.UtcNow).ReturnsAsync(new DateTimeOffset.UtcNow);
var response = await this.client.DeleteAsync("/foo/1");
Assert.Equal(HttpStatusCode.NoContent, response.StatusCode);
}
public void Dispose() => this.factory.VerifyAllMocks();
}
I've blogged about ASP.NET Core Integration Testing & Mocking using Moq. It's not simple and requires some setup but I hope it helps someone out. Here is the basic code you need using ASP.NET Core 3.1:
Startup
The ConfigureServices and Configure methods in your applications Startup class must be virtual. This is so that we can iherit from this class in our tests and replace production versions of certain services with mock versions.
public class Startup
{
private readonly IConfiguration configuration;
private readonly IWebHostingEnvironment webHostingEnvironment;
public Startup(IConfiguration configuration, IWebHostingEnvironment webHostingEnvironment)
{
this.configuration = configuration;
this.webHostingEnvironment = webHostingEnvironment;
}
public virtual void ConfigureServices(IServiceCollection services) =>
...
public virtual void Configure(IApplicationBuilder application) =>
...
}
TestStartup
In your test project, override the Startup class with one that registers the mock and the mock object with IoC.
public class TestStartup : Startup
{
private readonly Mock<IClockService> clockServiceMock;
public TestStartup(IConfiguration configuration, IHostingEnvironment hostingEnvironment)
: base(configuration, hostingEnvironment)
{
this.clockServiceMock = new Mock<IClockService>(MockBehavior.Strict);
}
public override void ConfigureServices(IServiceCollection services)
{
services
.AddSingleton(this.clockServiceMock);
base.ConfigureServices(services);
services
.AddSingleton(this.clockServiceMock.Object);
}
}
CustomWebApplicationFactory
In your test project, write a custom WebApplicationFactory that configures the HttpClient and resolves the mocks from the TestStartup, then exposes them as properties, ready for our integration test to consume them. Note that I'm also changing the environment to Testing and telling it to use the TestStartup class for startup.
Note also that I've implemented IDisposable's `Dispose method to verify all of my strict mocks. This means I don't need to verify any mocks manually myself. Verification of all mock setups happens automatically when xUnit is disposing the test class.
public class CustomWebApplicationFactory<TEntryPoint> : WebApplicationFactory<TEntryPoint>
where TEntryPoint : class
{
public CustomWebApplicationFactory()
{
this.ClientOptions.AllowAutoRedirect = false;
this.ClientOptions.BaseAddress = new Uri("https://localhost");
}
public ApplicationOptions ApplicationOptions { get; private set; }
public Mock<IClockService> ClockServiceMock { get; private set; }
public void VerifyAllMocks() => Mock.VerifyAll(this.ClockServiceMock);
protected override void ConfigureClient(HttpClient client)
{
using (var serviceScope = this.Services.CreateScope())
{
var serviceProvider = serviceScope.ServiceProvider;
this.ApplicationOptions = serviceProvider.GetRequiredService<IOptions<ApplicationOptions>>().Value;
this.ClockServiceMock = serviceProvider.GetRequiredService<Mock<IClockService>>();
}
base.ConfigureClient(client);
}
protected override void ConfigureWebHost(IWebHostBuilder builder) =>
builder
.UseEnvironment("Testing")
.UseStartup<TestStartup>();
protected override void Dispose(bool disposing)
{
if (disposing)
{
this.VerifyAllMocks();
}
base.Dispose(disposing);
}
}
Integration Tests
I'm using xUnit to write my tests. Note that the generic type passed to CustomWebApplicationFactory is Startup and not TestStartup. This generic type is used to find the location of your application project on disk and not to start the application.
I setup a mock in my test and I've implemented IDisposable to verify all mocks for all my tests at the end but you can do this step in the test method itself if you like.
Note also, that I'm not using xUnit's IClassFixture to only boot up the application once as the ASP.NET Core documentation tells you to do. If I did so, I'd have to reset the mocks between each test and also you would only be able to run the integration tests serially one at a time. With the method below, each test is fully isolated and they can be run in parallel. This uses up more CPU and each test takes longer to execute but I think it's worth it.
public class FooControllerTest : CustomWebApplicationFactory<Startup>
{
private readonly HttpClient client;
private readonly Mock<IClockService> clockServiceMock;
public FooControllerTest()
{
this.client = this.CreateClient();
this.clockServiceMock = this.ClockServiceMock;
}
[Fact]
public async Task GetFoo_Default_Returns200OK()
{
this.clockServiceMock.Setup(x => x.UtcNow).ReturnsAsync(new DateTimeOffset(2000, 1, 1));
var response = await this.client.GetAsync("/foo");
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}
}
xunit.runner.json
I'm using xUnit. We need to turn off shadown copying, so any separate files like appsettings.json are placed in the right place beside the application DLL file. This ensures that our application running in an integration test can still read the appsettings.json file.
{
"shadowCopy": false
}
appsettings.Testing.json
Should you have configuration that you want to change just for your integration tests, you can add a appsettings.Testing.json file into your application. This configuration file will only be read in our integration tests because we set the environment name to 'Testing'.
The best way to handle this is to factor out parts of your Startup that will need to be substituted during test. For example, instead of calling services.AddDbContext<MyContext>(...); directly in ConfigureServices, create a virtual private method like:
protected virtual void ConfigureDatabase(IServiceCollection services)
{
services.AddDbContext<MyContext>(...);
}
Then, in your test project, create a class like TestStartup which derives from your SUT's Startup class. Then, you can override these virtual methods to sub in your test services, mocks, etc.
Finally, just do something like:
builder
.UseEnvironment("Testing")
.UseStartup<TestStartup>();
You should create a fake startup:
public class FakeStartup : Startup
{
public FakeStartup(IConfiguration configuration)
: base(configuration)
{
}
public override void ConfigureServices(IServiceCollection services)
{
base.ConfigureServices(services);
// Your fake go here
//services.AddScoped<IService, FakeService>();
}
}
Then use it with IClassFixture<CustomWebApplicationFactory<FakeStartup>>.
Make sure to make your original ConfigureServices method virtual.

Get request outside a controller in .netcore

Is there a way I can get the response from a method without using a controller. I mean, in order to get the tenants from the database I use attribute binding and I get it from: "http://localhost:5000/api/tenants". Is there a way I can retrieve values without using a controller, like a service? For example in angular I use httpclient to get the response. Is there anything similar in .netcore 2 webapi? Thank, you!
For Controller, it uses UseMvc middleware to route the request to controller.
If you would not use controller, you could try custom middleware to return the data directly based on the request path.
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)
{
//your config
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
//your config
app.Map("/tenants", map => {
map.Run(async context => {
var dbContext = context.RequestServices.GetRequiredService<MVCProContext>();
var tenants = await dbContext.Users.ToListAsync();
await context.Response.WriteAsync(JsonConvert.SerializeObject(tenants));
});
});
app.Run(async context => {
await context.Response.WriteAsync($"Default response");
});
}
}

Testing ASP.NET 5 with Entity Framework 7 using in memory database

I am wanting to get ahold of the Context that I am injecting into the controllers during testing and modify the data in the "in memory" version of the database context.
So the controller looks like this
[Route("api/[controller]")]
public class TestController : Controller
{
private readonly TestContext _testContext;
public TestController(TestContext testContext)
{
_testContext = testContext;
}
[HttpGet]
public IActionResult Get()
{
return Ok(new { _testContext.Users });
}
}
The test looks like this
public class SiteTests
{
[Fact]
public async Task GetIt()
{
var server = TestServer.Create(app => { app.UseMvc(); }, services =>
{
services.AddMvc();
services.AddEntityFramework()
.AddSqlServer()
.AddDbContext<TestContext>(options => options.UseInMemoryDatabase());
services.AddScoped<TestContext, TestContext>();
});
var client = server.CreateClient();
var response = await client.GetAsync("http://localhost/api/test");
var content = await response.Content.ReadAsStringAsync();
Assert.True(response.IsSuccessStatusCode);
}
}
I would love to somehow get ahold of the context before the client gets the request and modify what data will be coming back from the database context.
I have the test project in GitHub
If you're targeting .NET Core, you won't be able to make use of any automatic mocking frameworks.
The best you can do is make all your methods in TestContext virtual, then extend it in your unit tests.
public class IntegrationTestContext : TestContext
{
// override methods here
}
You can then use
var context = new IntegrationTestContext();
services.AddInstance<TestContext>(context);
You can also capture any extra information you want in IntegrationTestContext and access it from within your test.