More than twenty 'IServiceProvider'. Unit Test - c#

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);
}
}

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.

ServiceBusTrigger Unit testing with XUNIT

I have an Azure Function as below. In the following code, I'm adding a product to the database using Repository & EF Core. Is there any way we can test it using Xunit? Currently, I'm testing this using Azure Service Bus Explorer.
[FunctionName("SaveProductData")]
public void Run([ServiceBusTrigger("mytopicname", Connection = "ServiceBusConnectionString")]
ProductItemUpdate message, ILogger log)
{
var product = new Domain.Entities.Product()
{
... prop init goes here.....
};
log.LogInformation($"Processed Product - Sain: {message.ProductId}");
_productRepository.Add(product);
}
To elaborate on scottdavidwalker's comment with a full example:
public class SaveProductDataTests
{
private readonly Mock<IProductRepository> _mockProductRepository = new Mock<IProductRepository>();
private readonly Mock<ILogger> _mockLogger = new Mock<ILogger>();
private readonly SaveProductData _saveProductData;
public SaveProductDataTests()
{
_saveProductData = new SaveProductData(_mockProductRepository.Object);
}
[Fact]
public void Given_When_Then()
{
// arrange
var productItemUpdate = new ProductItemUpdate();
// act
_saveProductData.Run(productItemUpdate, _mockLogger.Object);
// assert
_mockProductRepository.Verify(x => x.Add(It.Is<Product>(p => p.SomeProperty == "xyz")));
}
}
You need to create an instance of the class you are testing and mock the dependencies.
The Azure function is essentially a method (.Run()) inside the class which you can call on the instance.
In your unit test, you create the data to trigger the method and then make assertions on your mocks for what you expect to happen when the code runs for real.

XUnit: ICollectionFixture instance not getting shared between test methods of a test class

I have created ICollectionFixture implementation in hope to create a database instance and share it among various test classes. But, its just not happening and the DBContext gets created on every test method call. On debugging, I can clearly see that after every test method completion , Dispose method of my InMemoryDbContextFixture class gets called , and so every time , a new instance of DBContext gets created which does not saves any of the data that I passed through my first test method..
XUnit document clearly says that when you want to create a single test context and share it among tests in several test classes, we can create ICollectionFixture implementation.
What does sharing mean, when we always are creating a new instance of DBContext? Please help me understand such behavior in name of sharing the instance.
I can achieve the present behavior using
Static classes
as well. So why to use ICollectionFixture. This interface should prevent me from creating new instance of DBContext for every test case.
My Fixture class goes like
public class InMemoryDbContextFixture : IDisposable
{
private static bool _isdbInitialized = false;
static MyDbContext databaseContext = null;
private readonly object _lock = new object();
public InMemoryDbContextFixture()
{
lock (_lock)
{
if (!_isdbInitialized)
{
var options = new DbContextOptionsBuilder<MyDbContext>()
.UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
.Options;
databaseContext = new MyDbContext(options);
databaseContext.Database.EnsureDeleted(); //Ensure first old db is deleted.
databaseContext.Database.EnsureCreated();
_isdbInitialized = true; //db is initialized for the first time.
// ... initialize data in the test database ...
Context = databaseContext;
}
}
}
public void Dispose()
{
this.Context.Dispose();
}
public MyDbContext Context
{
get { return databaseContext; }
private set { }
}
}
ICollectionFixture implementation.
[CollectionDefinition("SharedTestDatabaseDemo")]
public class SharedTestDatabaseDBContextCollection : ICollectionFixture<InMemoryDbContextFixture>
{
// This class has no code, and should never be created. Its purpose is simply
// to be the place to apply [CollectionDefinition] and all the
// ICollectionFixture<> interfaces.
}
My Test Class
//using namespaces
namespace Project.TestControllers
{
[Collection("SharedTestDatabaseDemo")]
public class TestAuthController
{
private readonly MyDbContext myDbContext = null;
public TestAuthController(InMemoryDbContextFixture dbcontextForTest)
{
//Initialize non-mocked objects.
nimblyDbContext = dbcontextForTest.Context;
//Arrange mocks
objAuthCountroller = new AuthController(mock1.Object, configSettings, myDbContext,
mock2.Object, mock3.Object);
}
[Fact]
public async Task Test_LoginUserWithCorrectCredentials()
{
//Arrange dependencies.
InputModel logincred = new InputModel() {
Email = "XunitTestUser#testmail.com",
Password = "Pass#9799",
RememberMe = true
};
myDbContext.AspNetUserTokens.Add(new AspNetUserTokens
{
UserId = "2",
LoginProvider = "JWT",
Name = "XunitTestUser#testmail.com"
});
myDbContext.AspNetUsers.Add(new AspNetUsers
{
Id = "2",
Name = "XunitTestUser1",
UserName = "XunitTestUser1",
Email = "XunitTestUser#testmail.com",
NormalizedEmail = "XUNITTESTUSER#TESTMAIL.COM",
EmailConfirmed = true
});
await myDbContext.SaveChangesAsync();
//ACT
var result = await objAuthCountroller.Login(logincred);
Assert.NotNull(result);
}
[Fact]
public async Task Test_ShouldNotAuthenticateUserWithInvalidPassword()
{
InputModel logincred = new InputModel();
logincred.Email = "rahul#testmail.com";
logincred.Password = "InvalidPassword";
logincred.RememberMe = true;
var result = await objAuthCountroller.Login(logincred);
Assert.NotNull(result);
}
}
}
Yes, on running all the test cases at one go, I got the answer..
Actually , we might get this issue when we are trying to test only one Test method and not the entire Test Project. I got confused because when trying to debug Test methods individually in the test class.
Now, when I run all the run cases in visual studio , I can clearly see that ICollectionFixture instance is created only once and shared among various test classes using the collection.

Retrieving ASP.NET Core 3.1 database context in Xunit integration test

I've been creating integration tests using Xunit for a .NET Core 3.1 web app that uses a database. For testing I've swapped to an in-memory database following along from the Microsoft Documentation. The CustomWebApplicationFactory code is:
public class CustomWebApplicationFactory<TStartup> : WebApplicationFactory<TStartup> where TStartup : class
{
protected override void ConfigureWebHost(IWebHostBuilder webHostBuilder)
{
webHostBuilder.ConfigureServices(services =>
{
// Remove the app's database registration.
var serviceDescriptor = services
.SingleOrDefault(d => d.ServiceType == typeof(DbContextOptions<MyDbContext>));
if (serviceDescriptor != null)
{
services.Remove(serviceDescriptor);
}
// Add MyDbContext using an in-memory database for testing.
services.AddDbContext<MyDbContext>(options =>
{
options.UseInMemoryDatabase("InMemoryDbForTesting");
});
var servicesProvider = services.BuildServiceProvider();
// Create a scope to obtain a reference to the database context (MyDbContext).
using (var serviceScope = servicesProvider.CreateScope())
{
var scopedServices = serviceScope.ServiceProvider;
var db = scopedServices.GetRequiredService<MyDbContext>();
var logger = scopedServices.GetRequiredService<ILogger<CustomWebApplicationFactory<TStartup>>>();
db.Database.EnsureCreated(); // Ensure the database is created.
try
{
DatabaseSeeding.voidInitialiseCazIdentityProviderDatabase(db); // Seed the database with test data.
}
catch (Exception ex)
{
logger.LogError(ex, $"An error occurred seeding the database with data. Error: {ex.Message}");
}
}
});
}
My basic page tests work fine with this arrangement, but I now want to check to see if the in-memory database has been corrected modified by the integration test. Whatever is happening, the reference to the database is not ending up in the Xunit DI container (if such a thing exists). My test class is initialised with the following code:
public class IdpUserServiceTests : IClassFixture<CustomWebApplicationFactory<Startup>>
{
private readonly CustomWebApplicationFactory<Startup> _webApplicationFactory;
private readonly ITestOutputHelper _testOutput;
private readonly HttpClient _httpClient;
private readonly MyDbContext _myDbContext;
public IdpUserServiceTests(CustomWebApplicationFactory<Startup> webApplicationFactory, MyDbContext myDbContext, ITestOutputHelper testOutput)
{
_webApplicationFactory = webApplicationFactory;
_myDbContext = myDbContext;
_testOutput = testOutput;
_httpClient = _webApplicationFactory.CreateClient();
}
//Tests
but in attempting to run a test, I get the following error:
The following constructor parameters did not have matching fixture data: MyDbContext objMyDbContext
I'm looking for the correct way to access the in-memory database - it's obviously not via constructor injection. I've had a go with this answer - Access in memory dbcontext in integration test - but things seem to have changed between 2.2 and 3.1.
It turned out that the Access in memory dbcontext in integration test answer referenced above was not quite right for ASP.NET Core 3.1. The scopeFactory has to be found from a different source:
var scopeFactory = _webApplicationFactory.Services.GetService<IServiceScopeFactory>();
So the test class now looks like:
public class ServiceTests : IClassFixture<CustomWebApplicationFactory<Startup>>
{
private readonly CustomWebApplicationFactory<Startup> _webApplicationFactory;
private readonly ITestOutputHelper _testOutput;
private readonly HttpClient _httpClient;
public ServiceTests(CustomWebApplicationFactory<Startup> webApplicationFactory, ITestOutputHelper testOutput)
{
_webApplicationFactory = webApplicationFactory;
_testOutput = testOutput;
_httpClient = _webApplicationFactory.CreateClient();
}
[Fact]
public async Task AServiceTest()
{
// Do some HTTP client stuff that alters the in-memory database
var scopeFactory = _webApplicationFactory.Services.GetService<IServiceScopeFactory>();
using(var scope = scopeFactory.CreateScope())
{
var myDataContext = scope.ServiceProvider.GetService<MyDataContext>();
// Query the in-memory database
}
}
}

"OneTimeSetUp: No suitable constructor was found" Integration testing with ApplicationDbContext

I have an issue regarding integration testing within a razor application combined with MVVM. All my classes use ApplicationDbContext.
The Test class:
[TestFixture]
public class ApiParserControllerTests
{
private readonly ApplicationDbContext _dbContext;
public ApiParserControllerTests(ApplicationDbContext dbContext)
{
_dbContext = dbContext;
}
[Test]
public void IsOptionValid_Teacher_ShouldReturnTrue()
{
var model = new ApiParserController(_dbContext);
var assign = model.IsOptionValid("Teacher");
Assert.AreEqual(true, assign.Value);
}
The method class:
public class ApiParserController : Controller
{
private readonly ApplicationDbContext _dbContext;
public ApiParserController(ApplicationDbContext dbContext)
{
_dbContext = dbContext;
}
[HttpPost]
public JsonResult IsOptionValid(string Option)
{
return Json(_dbContext.Import.Any(x => x.Option.ToLower() == Option.ToLower()));
}
}
Running this gives me the error in the title. I have tried adding an empty constructor to solve this problem, however this just makes the ApplicationDbContext null.
What am I missing here?
EDIT:
I have added a unit test for this method that mocks the database using the inMemory:
[Test]
public void IsOptionValid_Teacher_ShouldReturnTrue()
{
//Arrange
var optionsbuilder = new DbContextOptionsBuilder<ApplicationDbContext>();
optionsbuilder.UseInMemoryDatabase(databaseName: "TeacherDB");
var _dbContext = new ApplicationDbContext(optionsbuilder.Options);
JsonResult json = new JsonResult(true);
_dbContext.ImportOption.Add(new ImportOption { Id = 1, isUnique = 1, Option = "Teacher" });
_dbContext.SaveChanges();
//Act
var model = new ApiParserController(_dbContext);
var assign = model.IsOptionValid("Teacher");
//Assert
Assert.AreEqual(true, assign.Value);
}
You defined a fixture with a constructor that takes an ApplicationDbContext. That means you need to supply NUnit with such an object so that it can call the constructor.
OTOH, by using TestFixture without any arguments, you are telling NUnit to use a default constructor. That's the source of the original error message, since the class had no default constructor.
Adding a default constructor makes it possible for NUnit to construct your fixture class, but that still doesn't give you the dbContext you need in order to run the test.
Normally, you would supply an argument to the fixture constructor by passing it as an argument to the TestFixtureAttribute. This may be difficult to do in your case and it may be simpler to either construct the context in OneTimeSetUp for the class or to mock the context. Which you do depends on exactly what you are trying to test, which isn't clear from the question.
In one of your comments, you mention wanting to use the "current dbContext". This might be the key, if you can explain it further. What do you mean by "current" here? Where is that context created? Then we can figure out how your test can get access to it.
Remove default constructor in your ApiParserControllerTests, use [SetUp] function instead.
[TestFixture]
public class ApiParserControllerTests
{
private ApplicationDbContext _dbContext;
[SetUp]
public void SetUp(){
// initialize here
_dbContext = new ApplicationDbContext();
}
[Test]
public void IsOptionValid_Teacher_ShouldReturnTrue()
{
var model = new ApiParserController(_dbContext);
var assign = model.IsOptionValid("Teacher");
Assert.AreEqual(true, assign.Value);
}
}

Categories

Resources