Open archigo opened 1 year ago
@archigo
I think the issue that you are running into with your code is that your IServiceScopeFactory
is always going to return the same IServiceScope
instance. So even though you may create multiple scope, all of the scopes are the same instance and will be then using the same AutoMocker
instance.
I think something like this is closer to what you want.
[TestMethod]
public void ScopedItemsAreResolvedWithinScopes()
{
AutoMocker mocker = new();
var scopeFacMock = mocker.GetMock<IServiceScopeFactory>();
scopeFacMock.Setup(x => x.CreateScope()).Returns(() => {
AutoMocker scopedMocker = new();
//Regsiter items that should be scoped
scopedMocker.With<IScopedService, ScopedService>();
Mock<IServiceScope> scope = new();
scope.Setup(x => x.ServiceProvider).Returns(() => scopedMocker);
return scope.Object;
});
using var scope1 = mocker.CreateScope();
using var scope2 = mocker.CreateScope();
IScopedService instance1 = scope1.ServiceProvider.GetRequiredService<IScopedService>();
IScopedService instance2 = scope2.ServiceProvider.GetRequiredService<IScopedService>();
Assert.AreNotSame(instance1, instance2);
}
It is also worth noting that AutoMocker
is not intended to be a full DI container (the fact it implements IServiceProvider
is more of a convenience). In a full DI implementation, the scoped AutoMocker
instance would reach back up to the parent AutoMocker
instance for things like singletons. This sort of behavior could be achieved with a custom resolver, but in most cases where a full DI container is needed, I would recommend just creating the real DI container, and registering mock instances where appropriate.
The issue is that there are mocks set up that needs to be available to on the scoped mocks as well.
I am resolving a service which is not concurrency safe, so it is important that a new service is resolved in each scope, but I still need to have the mocks that are returning external data in each scope as the service depends on them.
public class BaseTestClass {
protected baseAutoMocker;
public BaseTestClass(){
baseAutoMocker= new AutoMocker();
// setup mocks for all the things that always needs to be setup, like auth services and other stuff.
....
var scopeFacMock = baseAutoMocker.GetMock<IServiceScopeFactory>();
scopeFacMock.Setup(x => x.CreateScope()).Returns(() => {
AutoMocker scopedMocker = new(); // this somehow needs to copy the mock that each unit test adds to baseAutoMocker
//Register items that should be scoped
scopedMocker.With<IScopedService, ScopedService>();
Mock<IServiceScope> scope = new();
scope.Setup(x => x.ServiceProvider).Returns(() => scopedMocker);
return scope.Object;
});
}
}
[TestClass]
public class Tests : BaseTestClass {
[TestMethod]
public void SomeTest()
{
var someServiceMock = baseAutoMocker.GetMock<SomeService>();
someServiceMock.Setup(...) // this needs to be setup on all of the scoped mocks as well!
var sut = baseAutoMocker.CreateInstance<ServiceToBeTested>();
sut.Run()
Assert...
}
}
// code to be tested
Parallel.ForEach(operations, operation =>
{
var sp = _serviceScopeFactory.CreateScope().ServiceProvider;
var dbcontext = sp.GetRequiredService<DbContext>();
var someEntity = dbcontext.SomeEntities.First(x => x.Id == operation.Id);
var someService = sp.GetRequiredService<SomeService>();
// get some mocked data from someService to create some database chages
var data = someService.GetData();
someEntity.Something = data.Something;
...
dbcontext .SaveChanges();
});
We have a Parallel.ForEach that uses
IServiceScopeFactory
to make sure certain services are different instances.I am having a hard time figure out how to make this work when resolving through automocker.
I can mock the scope resolution to resolve to an automocker, but I can not seem to find a way to make that scoped automocker have the mocks that are set on the original automocker.
Our setup is that we have a base class that all our tests inherit from. The base class will set up an automocker with default configs and we add to those configs in each test.
Is there a convenient way to work with scoped services in automocker, or a way to copy the setup in an existing automocker to a new mocker?
In the above code (from our base setup class) the scopedMocker is missing the mock setup from individual tests