I am trying to access my in-memory database created for integration tests. My CustomWebApplicationFactory
file looks like this:
using System;
using System.Linq;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using amaranth.Data;
namespace amaranth.Tests
{
public class CustomWebApplicationFactory<TStartup>
: WebApplicationFactory<TStartup> where TStartup: class
{
public ApplicationDbContext GetDb()
{
return Services.GetService<ApplicationDbContext>() ?? throw new NullReferenceException("Unable to get DB from registered services");
}
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureServices(services =>
{
var descriptor = services.SingleOrDefault(
d => d.ServiceType ==
typeof(DbContextOptions<ApplicationDbContext>));
services.Remove(descriptor);
services.AddDbContext<ApplicationDbContext>(options =>
{
options.UseInMemoryDatabase("InMemoryDbForTesting");
});
services.AddAntiforgery(t =>
{
t.Cookie.Name = AntiForgeryTokenExtractor.AntiForgeryCookieName;
t.FormFieldName = AntiForgeryTokenExtractor.AntiForgeryFieldName;
});
var sp = services.BuildServiceProvider();
using (var scope = sp.CreateScope())
{
var scopedServices = scope.ServiceProvider;
var db = scopedServices.GetRequiredService<ApplicationDbContext>();
var logger = scopedServices
.GetRequiredService<ILogger<CustomWebApplicationFactory<TStartup>>>();
db.Database.EnsureCreated();
}
});
}
}
}
And this is the test in question, from the file IntegrationTests/AuthTests.cs
:
using System.Collections.Generic;
using System.Net;
using System.Net.Http.Headers;
using System.Security.Claims;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
using Microsoft.Net.Http.Headers;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using AngleSharp.Html.Dom;
using Xunit;
using amaranth.Tests.Helpers;
using Microsoft.EntityFrameworkCore;
using amaranth.Data;
namespace amaranth.Tests
{
public class AuthTests :
IClassFixture<CustomWebApplicationFactory<amaranth.Startup>>
{
private readonly CustomWebApplicationFactory<amaranth.Startup>
_factory;
public AuthTests(CustomWebApplicationFactory<amaranth.Startup> factory)
{
_factory = factory;
}
[Fact]
public async Task Post_FirsttoRegisterClaimsAdmin()
{
// Arrange
var client = _factory.CreateClient(
new WebApplicationFactoryClientOptions
{
AllowAutoRedirect = true
});
var initResponse = await client.GetAsync("/Identity/Account/Register");
var antiForgeryValues = await AntiForgeryTokenExtractor.ExtractAntiForgeryValues(initResponse);
var postRequest = new HttpRequestMessage(HttpMethod.Post, "/Identity/Account/Register");
postRequest.Headers.Add("Cookie", new CookieHeaderValue(AntiForgeryTokenExtractor.AntiForgeryCookieName, antiForgeryValues.cookieValue).ToString());
var formModel = new Dictionary<string, string>
{
{ AntiForgeryTokenExtractor.AntiForgeryFieldName, antiForgeryValues.fieldValue },
{ "Email", "[email protected]" },
{ "Password", "pas3w0!rRd" },
{ "ConfirmPassword", "pas3w0!rRd" },
};
postRequest.Content = new FormUrlEncodedContent(formModel);
var response = await client.SendAsync(postRequest);
response.EnsureSuccessStatusCode();
var postRequest2 = new HttpRequestMessage(HttpMethod.Post, "/Home/SetAdminToUser");
postRequest2.Headers.Add("Cookie", new CookieHeaderValue(AntiForgeryTokenExtractor.AntiForgeryCookieName, antiForgeryValues.cookieValue).ToString());
var formModel2 = new Dictionary<string, string>
{
{ AntiForgeryTokenExtractor.AntiForgeryFieldName, antiForgeryValues.fieldValue },
{ "check1", "True" },
{ "check2", "True" },
{ "check3", "True" },
};
postRequest2.Content = new FormUrlEncodedContent(formModel2);
var response2 = await client.SendAsync(postRequest2);
response2.EnsureSuccessStatusCode();
Assert.Equal("http://localhost/Home/ClaimAdmin", response.RequestMessage.RequestUri.ToString());
Assert.Equal("http://localhost/Admin/MasterWalletList", response2.RequestMessage.RequestUri.ToString());
//TODO check the db to see if the user is now admin
var db = _factory.GetDb();
var user = db.UserRoles.FirstOrDefault(ur => ur.RoleId == "admin");
Assert.NotNull(user);
Assert.Equal(user?.UserId, "TODO GET USER ID");
#endregion
}
}
public class TestAuthHandler : AuthenticationHandler<AuthenticationSchemeOptions>
{
public TestAuthHandler(IOptionsMonitor<AuthenticationSchemeOptions> options,
ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock)
: base(options, logger, encoder, clock)
{
}
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
{
var claims = new[] { new Claim(ClaimTypes.Name, "Test user") };
var identity = new ClaimsIdentity(claims, "Test");
var principal = new ClaimsPrincipal(identity);
var ticket = new AuthenticationTicket(principal, "Test");
var result = AuthenticateResult.Success(ticket);
return Task.FromResult(result);
}
}
}
On this line var db = _factory.GetDb();
I get this error:
Request finished HTTP/1.1 GET http://localhost/Admin/MasterWalletList - - - 200 - text/html; charset=utf-8 11.1544ms
[xUnit.net 00:00:02.78] amaranth.Tests.AuthTests.Post_FirsttoRegisterClaimsAdmin [FAIL]
Failed amaranth.Tests.AuthTests.Post_FirsttoRegisterClaimsAdmin [1 s]Error Message:
System.InvalidOperationException : Cannot resolve scoped service 'amaranth.Data.ApplicationDbContext' from root provider.
Stack Trace:
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteValidator.ValidateResolution(Type serviceType, IServiceScope scope, IServiceScope rootScope)
at Microsoft.Extensions.DependencyInjection.ServiceProvider.GetService(Type serviceType, ServiceProviderEngineScope serviceProviderEngineScope)
at Microsoft.Extensions.DependencyInjection.ServiceProvider.GetService(Type serviceType)
at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetService[T](IServiceProvider provider) at amaranth.Tests.CustomWebApplicationFactory`1.GetDb() in /path/to/project/directory/amaranth.Tests/CustomWebApplicationFactory.cs:line 17 at amaranth.Tests.AuthTests.Post_FirsttoRegisterClaimsAdmin() in /path/to/project/directory/amaranth.Tests/IntegrationTests/AuthTests.cs:line 128
--- End of stack trace from previous location ---
info: Microsoft.EntityFrameworkCore.Infrastructure[10403]
What am I doing wrong? Why can't I access amaranth.Data.ApplicationDbContext
?
CodePudding user response:
I solved this by making var db
scoped. So you can fix this by replacing:
var db = _factory.GetDb();
var user = db.UserRoles.FirstOrDefault(ur => ur.RoleId == "admin");
Assert.NotNull(user);
Assert.Equal(user?.UserId, "TODO GET USER ID");
With this:
Microsoft.AspNetCore.Identity.IdentityUserRole<string>? userRl;
Microsoft.AspNetCore.Identity.IdentityUser? user;
using (var scope = _factory.Services.CreateScope())
{
var scopedServices = scope.ServiceProvider;
var db = scopedServices.GetRequiredService<ApplicationDbContext>();
user = db.Users.FirstOrDefault();
userRl = db.UserRoles.FirstOrDefault();
}
Assert.NotNull(user);
Assert.True(userRl?.UserId == user?.Id);