Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Register mock/service as scoped #188

Open
archigo opened this issue Feb 16, 2023 · 2 comments
Open

Register mock/service as scoped #188

archigo opened this issue Feb 16, 2023 · 2 comments
Labels

Comments

@archigo
Copy link

archigo commented Feb 16, 2023

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?

            // the base mocker that all tests use
            autoMocker = CreateDefaultAutoMocker();

            var scopeMock = autoMocker.GetMock<IServiceScope>();
            scopeMock.Setup(x => x.ServiceProvider).Returns(() =>
            {
                var scopedMocker = CreateDefaultAutoMocker();
                // copy all setup from base mocker here and overwrite the scoped service?
                scopedMocker.Use<IScopedService>(new ScopedService());
                return scopedMocker; 
            });

            var scopeFacMock = autoMocker.GetMock<IServiceScopeFactory>();
            scopeFacMock.Setup(x => x.CreateScope()).Returns(scopeMock.Object);

In the above code (from our base setup class) the scopedMocker is missing the mock setup from individual tests

@Keboo
Copy link
Collaborator

Keboo commented Feb 16, 2023

@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.

@Keboo Keboo added the question label Feb 16, 2023
@archigo
Copy link
Author

archigo commented Feb 16, 2023

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants