AspNetCore 6 / Unit Testing with UnserManager without Mocking it - c#

I am using AspNetCore 6 and trying to use the UserManager and RoleManager in my UnitTests.
I don't like to mock them because I want to test scenarios where I need access to different users stored in the database.
I have found a way to get to the UserManager, but then the DB context seems to be different.
I have oriented myself on this
[TestClass]
public abstract class BaseTest
{
internal DBContext DBContext;
internal UserManager<ApplicationUser> AppUserManager;
internal RoleManager<ApplicationRole> AppRoleManager;
internal HttpContext HttpContext;
internal IConfiguration AppConfiguration = new ConfigurationBuilder().AddJsonFile("appsettings.Test.json").Build();
[TestInitialize]
public virtual void TestSetup()
{
// Mock the Http Context accessor
var mockHttpContextAccessor = MockHttpContextAccessor("MrX", "MrX");
HttpContext = mockHttpContextAccessor.Object.HttpContext;
IServiceCollection serviceCollection = new ServiceCollection();
// Add the DbContext
serviceCollection
.AddDbContext<DBContext>(options =>
{
options.UseOracle(AppConfiguration["OracleUnitTestDbConnection"])
.UseLazyLoadingProxies()
.EnableSensitiveDataLogging();
})
.AddSingleton(mockHttpContextAccessor.Object)
.AddLogging()
.AddIdentity<ApplicationUser, ApplicationRole>()
.AddEntityFrameworkStores<DBContext>()
.AddUserManager<UserManager<ApplicationUser>>()
.AddRoleManager<RoleManager<ApplicationRole>>()
.AddDefaultTokenProviders();
DBContext = serviceCollection.BuildServiceProvider().GetService<DBContext>();
// When initializing like this it seems to get a different DBContext than the one above
AppUserManager = serviceCollection.BuildServiceProvider().GetService<UserManager<ApplicationUser>>();
AppRoleManager = serviceCollection.BuildServiceProvider().GetService<RoleManager<ApplicationRole>>();
DBContext.Database.BeginTransaction(); // Transaction will be Rollbacked in [TestCleanup]
MrXRole = CreateRole("MrXRole"); // Adds the ApplicationRole "MrXRole" to DBContext
MrX = CreateUser("MrX", MrXRole, "Test1234!"); // Adds the ApplicationUser "MrX" to DBContext
MrYRole = CreateRole("MrYRole"); // Adds the ApplicationRole "MrYRole" to DBContext
MrY = CreateUser("MrY", MrYRole, "Test1234!"); // Adds the ApplicationUser "MrY" to DBContext
DBContext.SaveChanges();
var test1 = DBContext.Users.Count(); // test1 = 2 --> as expected
var test2 = AppUserManager.Users.Count(); // test2 = 0 --> not expected --> different DBContext
}
}
test2 probably has a different DB context. When I debug, I don't see the two previously created users in the AppUserManager context. In the DbContext they are there.
I really hope someone could help me out here as I invested a lot of time on this without any success

Could fix this on my own :)
The clue is that the ServiceProvider is built multiple times when calling serviceCollection.BuildServiceProvider().
So each ServiceProvider has different DBContext.
var serviceProvider = serviceCollection.BuildServiceProvider();
DBContext = serviceProvider.GetService<DBContext>();
AppUserManager = serviceProvider.GetService<UserManager<ApplicationUser>>();
AppRoleManager = serviceProvider.GetService<RoleManager<ApplicationRole>>();

Related

Using in memory context in service layer unit tests

I have a service called UserService (this is obviously made up)
private readonly IAuthenticationService _authenticationService;
private readonly IUserRepository _userRepository;
public UserService(
IUserRepository userRepository,
IAuthenticationService authenticationService)
{
_userRepository = userRepository;
_authenticationService = authenticationService;
}
public async Task UpdateUser(UserDTO userDTO)
{
var authenticationDetails = await _authenticationService.Authenticate(userDTO.Id);
if (authenticationDetails.Success)
{
var user = _userRepository.GetUser(userDTO.Id);
user.Update(userDTO);
_userRepository.SaveChanges();
}
}
The IUserRepository implementation looks like this:
public UserRepository : Repository
{
private readonly UserContext _userContext;
public UserRepository(UserContext context) : base(context)
{
_userContext = context;
}
...
}
What I want to do, is have two service tests like these:
UpdateUser_WithSuccessfulAuthentication_UpdatesUser
UpdateUser_WithFailedAuthentication_DoesNothing
If following correct unit testing principles, I want to only consider the output (in this case, check if the user has been updated).
This way I can test the business logic inside the service method UpdateUser (which in this case is that single condition that checks if the user is authenticated. In reality there might be some more complex logic).
Currently I have considered one option for this, but haven't been able to make it work:
I can mock the IAuthenticationService using Moq, and have an in-memory-database context that would be used for the IUserRepository. So the first unit test would look something like this:
public void UpdateUser_WithSuccessfulAuthentication_UpdatesUser()
{
// Arrange
var authenticationServiceMock = new Mock<IAuthenticationService>();
var contextOptions = new DbContextOptionsBuilder<UserContext>()
.UseInMemoryDatabase("Db")
.Options;
using var context = new UserContext(contextOptions);
var userRepository = new UserRepository(context);
var userService = new UserService(userRepository, authenticationServiceMock.Object);
... setup the authentication service methods, in this case Authenticate
var userDTOToCreate = GetUserTestDoubleToCreate();
var userDTOToUpdate = GetUserTestDoubleToUpdate();
userRepository.Insert(userDTOToCreate);
// Act
userService.UpdateUser(userDTOToUpdate);
// Assert
... assert user was updated etc.
}
The problem with this is I cannot use the context inside this service since it has already been disposed.
I have no other ideas on how to do this.

More than twenty 'IServiceProvider'. Unit Test

I have this error message :
An error was generated for warning 'Microsoft.EntityFrameworkCore.Infrastructure.ManyServiceProvidersCreatedWarning': More than twenty 'IServiceProvider' instances have been created for internal use by Entity Framework. This is commonly caused by injection of a new singleton service instance into every DbContext instance. For example, calling 'UseLoggerFactory' passing in a new instance each time--see https://go.microsoft.com/fwlink/?linkid=869049 for more details. This may lead to performance issues, consider reviewing calls on 'DbContextOptionsBuilder' that may require new service providers to be built. This exception can be suppressed or logged by passing event ID 'CoreEventId.ManyServiceProvidersCreatedWarning' to the 'ConfigureWarnings' method in 'DbContext.OnConfiguring' or 'AddDbContext'.
When I run all Unit Test together
Setup
private readonly DbContextOptions<ApplicationDbContext> _contextOptions;
private readonly DbContextOptions<ApplicationDbContext> _inMemoryContextOptions;
public TestConstructor()
{
// Test for real database READ
_contextOptions = new DbContextOptionsBuilder<ApplicationDbContext>()
.UseSqlServer(_connectionString)
.Options;
// Test InMemory CREATE UPDATE DELETE
_inMemoryContextOptions = DbContextOptionsBuilder();
SeedInMemoryTestDb(_inMemoryContextOptions);
}
private static DbContextOptions<ApplicationDbContext> DbContextOptionsBuilder()
{
return new DbContextOptionsBuilder<ApplicationDbContext>()
.UseInMemoryDatabase(Guid.NewGuid().ToString(),new InMemoryDatabaseRoot())
.Options;
}
Unit Test
[FACT]
public void Test1()
await using var context = new ApplicationDbContext(_contextOptions);
//... Assert.Equal()
[FACT]
public void Test2()
await using var context = new ApplicationDbContext(_inMemoryContextOptions);
//... Assert.Equal()
I have both Setup and Unit Test in 5 or 6 class.
I think I need to re-use the same context for every test but I don't achieve to do that.
[CollectionDefinition("SharedDbContext")]
public class DatabaseCollection : ICollectionFixture<DatabaseFixture> { }
public class DatabaseFixture : IDisposable
{
public ApplicationDbContext ApplicationDbContext;
public ApplicationDbContext InMemoryApplicationDbContext;
public DatabaseFixture()
{
// Test for real database READ
var contextOptions = new DbContextOptionsBuilder<ApplicationDbContext>()
.UseSqlServer(_connectionString)
.Options;
//// Test InMemory CREATE UPDATE DELETE
var inMemoryContextOptions = DbContextOptionsBuilder();
ApplicationDbContext = new ApplicationDbContext(contextOptions);
InMemoryApplicationDbContext = new ApplicationDbContext(inMemoryContextOptions);
SeedInMemoryTestDb(inMemoryContextOptions);
}
}

How to I access the DbContext of EF core from another project when used in ASP.NET core?

I followed the pattern to use EF Core with ASP.NET core and all is well. But recently I created a 'Calculation' project and want to make database calls from it.
The problem is I don't know how to create a new DbContextOptions. In my code that is done with
services.AddDbContext<RetContext>(options => options
.UseLazyLoadingProxies()
.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
But in a new .NET core class I need to provide it manually. How do I do this ? My code is like this:
public static class LoadData
{
public static IConfiguration Configuration { get; }
public static RefProgramProfileData Load_RefProgramProfileData(string code)
{
// var optionsBuilder = new DbContextOptionsBuilder<RetContext>();
// optionsBuilder.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"));
//How do I make an optionsbuilder and get the configuration from the WEB project?
UnitOfWork uow = new UnitOfWork(new RetContext(optionsBuilder));
var loadedRefProgramProfileData = uow.RefProgramProfileDataRepository
.Find(x => x.ProgramCode == code).FirstOrDefault();
return loadedRefProgramProfileData;
}
}
You may instantiate your DbContext like this:
var builder = new ConfigurationBuilder().SetBasePath(Directory.GetCurrentDirectory()).AddJsonFile("appsettings.json");
var configuration = builder.Build();
var optionsBuilder = new DbContextOptionsBuilder<RetContext>();
optionsBuilder.UseSqlServer(configuration.GetConnection("DefaultConnection"));
_context = new RetContext(optionsBuilder.Options);
However, the ideal is to use dependency injection. Let's say you have a class CalculationService in your other project. For that, you need to register that class as a service that can be injected:
services.AddScoped<CalculationService>();
Then your class can receive DbContext (or any other services) through DI:
public class CalculationService
{
private RetContext _context;
public CalculationService(RetContext context)
{
_context = context;
}
}
Naturally, you won't be able to instantiate your class manually like this:
var service = new CalculationService();
Instead, you'd need to make whatever class needs to use your CalculationService to also receive it through DI and make that class injectable as well.

Reconfigure dependencies when Integration testing ASP.NET Core Web API and EF Core

I'm following this tutorial
Integration Testing with Entity Framework Core and SQL Server
My code looks like this
Integration Test Class
public class ControllerRequestsShould : IDisposable
{
private readonly TestServer _server;
private readonly HttpClient _client;
private readonly YourContext _context;
public ControllerRequestsShould()
{
// Arrange
var serviceProvider = new ServiceCollection()
.AddEntityFrameworkSqlServer()
.BuildServiceProvider();
var builder = new DbContextOptionsBuilder<YourContext>();
builder.UseSqlServer($"Server=(localdb)\\mssqllocaldb;Database=your_db_{Guid.NewGuid()};Trusted_Connection=True;MultipleActiveResultSets=true")
.UseInternalServiceProvider(serviceProvider);
_context = new YourContext(builder.Options);
_context.Database.Migrate();
_server = new TestServer(new WebHostBuilder()
.UseStartup<Startup>()
.UseEnvironment(Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT")));
_client = _server.CreateClient();
}
[Fact]
public async Task ReturnListOfObjectDtos()
{
// Arrange database data
_context.ObjectDbSet.Add(new ObjectEntity{ Id = 1, Code = "PTF0001", Name = "Portfolio One" });
_context.ObjectDbSet.Add(new ObjectEntity{ Id = 2, Code = "PTF0002", Name = "Portfolio Two" });
// Act
var response = await _client.GetAsync("/api/route");
response.EnsureSuccessStatusCode();
// Assert
var result = Assert.IsType<OkResult>(response);
}
public void Dispose()
{
_context.Dispose();
}
As I understand it, the .UseStartUp method ensures the TestServer uses my startup class
The issue I'm having is that when my Act statement is hit
var response = await _client.GetAsync("/api/route");
I get an error in my startup class that the connection string is null. I think My understanding of the problem is that when my controller is hit from the client it injects my data repository, which in turn injects the db context.
I think I need to configure the service as part of the new WebHostBuilder section so that it used the context created in the test. But I'm not sure how to do this.
ConfigureServices method in Startup.cs
public void ConfigureServices(IServiceCollection services)
{
// Add framework services
services.AddMvc(setupAction =>
{
setupAction.ReturnHttpNotAcceptable = true;
setupAction.OutputFormatters.Add(new XmlDataContractSerializerOutputFormatter());
setupAction.InputFormatters.Add(new XmlDataContractSerializerInputFormatter());
});
// Db context configuration
var connectionString = Configuration["ConnectionStrings:YourConnectionString"];
services.AddDbContext<YourContext>(options => options.UseSqlServer(connectionString));
// Register services for dependency injection
services.AddScoped<IYourRepository, YourRepository>();
}
#ilya-chumakov's answer is awesome. I just would like to add one more option
3. Use ConfigureTestServices method from WebHostBuilderExtensions.
The method ConfigureTestServices is available in the Microsoft.AspNetCore.TestHost version 2.1(on 20.05.2018 it is RC1-final). And it lets us override existing registrations with mocks.
The code:
_server = new TestServer(new WebHostBuilder()
.UseStartup<Startup>()
.ConfigureTestServices(services =>
{
services.AddTransient<IFooService, MockService>();
})
);
Here are two options:
1. Use WebHostBuilder.ConfigureServices
Use WebHostBuilder.ConfigureServices together with WebHostBuilder.UseStartup<T> to override and mock a web application`s DI registrations:
_server = new TestServer(new WebHostBuilder()
.ConfigureServices(services =>
{
services.AddScoped<IFooService, MockService>();
})
.UseStartup<Startup>()
);
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
//use TryAdd to support mocking IFooService
services.TryAddTransient<IFooService, FooService>();
}
}
The key point here is to use TryAdd methods inside the original Startup class. Custom WebHostBuilder.ConfigureServices is called before the original Startup, so the mocks are registered before the original services. TryAdd doesn't do anything if the same interface has already been registered, thus the real services will not be even touched.
More info: Running Integration Tests For ASP.NET Core Apps.
2. Inheritance / new Startup class
Create TestStartup class to re-configure ASP.NET Core DI. You can inherit it from Startup and override only needed methods:
public class TestStartup : Startup
{
public TestStartup(IHostingEnvironment env) : base(env) { }
public override void ConfigureServices(IServiceCollection services)
{
//mock DbContext and any other dependencies here
}
}
Alternatively TestStartup can be created from scratch to keep testing cleaner.
And specify it in UseStartup to run the test server:
_server = new TestServer(new WebHostBuilder().UseStartup<TestStartup>());
This is a complete large example: Integration testing your asp .net core app with an in memory database.

How to mock out the UserManager in ASP.NET 5

I am writing a UI for managing users in an ASP.NET 5 app. I need to show any errors returned by the UserManager in the UI. I have the IdentityResult errors being passed back in the view model but I am a touch adrift when it comes to testing my code.
What is the best way to Mock the UserManager in ASP.NET 5?
Should I be inheriting from UserManager and overriding all the methods I am using and then injecting my version of UserManager into an instance of the Controller in my test project?
I have managed it with the help of the MVC Music Store sample application.
In my Unit Test class, I set up the database context and UserManager like this:
public class DatabaseSetupTests : IDisposable
{
private MyDbContext Context { get; }
private UserManager<ApplicationUser> UserManager { get; }
public DatabaseSetupTests()
{
var services = new ServiceCollection();
services.AddEntityFramework()
.AddInMemoryDatabase()
.AddDbContext<MyDbContext>(options => options.UseInMemoryDatabase());
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<MyDbContext>();
// Taken from https://github.com/aspnet/MusicStore/blob/dev/test/MusicStore.Test/ManageControllerTest.cs (and modified)
// IHttpContextAccessor is required for SignInManager, and UserManager
var context = new DefaultHttpContext();
context.Features.Set<IHttpAuthenticationFeature>(new HttpAuthenticationFeature());
services.AddSingleton<IHttpContextAccessor>(h => new HttpContextAccessor { HttpContext = context });
var serviceProvider = services.BuildServiceProvider();
Context = serviceProvider.GetRequiredService<MyDbContext>();
UserManager = serviceProvider.GetRequiredService<UserManager<ApplicationUser>>();
}
....
}
Then I can use the UserManager in my unit tests, for example:
[Fact]
public async Task DontCreateAdminUserWhenOtherAdminsPresent()
{
await UserManager.CreateAsync(new ApplicationUser { UserName = "some#user.com" }, "IDoComplyWithTheRules2016!");
...
}
If your Dependency Injector is not able to resolve an IHttpContextAccessor then you will not be able to create a UserManager instance due to it being dependent on it.
I think (and this is just an assumption), that with Asp.Net 5, the UserManager does take care of refreshing cookie based claims when you change them (claims, roles...) for a user and therefore requires some HttpContext for login / logout actions and cookie access.

Categories

Resources