2

I'm using ASP.NET core dependency injection in my test project, to set up the text context used by my tests (so I can't use constructor injection here). In ConfigureServices, I register the services, which works fine:

  public void ConfigureServices(IServiceCollection services)
  {
      // Scans assemblies and adds MediatR handlers, preprocessors, and postprocessors implementations to the container.            
      services.AddMediatR(
       typeof(Application.Logic.Queries.FindUserByEmailAddressHandler));            
      services.AddTransient<ILocalDb, LocalDb>(l => new LocalDb(null));
      services.AddTransient<IUnitOfWork, UnitOfWork>(uow => new UnitOfWork(""));            
      services.AddTransient<IUserRepository, UserRepository>();
  }

However, when trying to get an instance of my unit of work, I have a problem:

   var localDb = serviceProvider.GetService<ILocalDb>();
   var unitOfWork = serviceProvider.GetService<IUnitOfWork>(); <- need to pass constructor parameter

You see, the UnitOfWork constructor accepts a connection string, and I have to pass this connection string coming from localDb (LocalDb creates a test database on the fly).

In StructureMap I could pass a parameter to the constructor when getting an instance as follows:

  x.For<IUnitOfWork>().Use<UnitOfWork>().Ctor<string>().Is(localDb.ConnectionString); });

How can I do this with ASP.NET Core dependency injection?

5
  • var localDb = serviceProvider.GetService<ILocalDb>(); is wrong to start with, known as the service locator anti-pattern. Why exactly are you doing that? Another problem is that a service (UnitOfWork) is receiving a connection string and that only means that the service itself is building another dependency. Overall, the structure is completely wrong Commented Feb 15, 2019 at 19:40
  • Make some marker Interfaces that derived from IUnitOfWork and make some base repositories, now you could have some base service with same functionality and seperated units... Commented Feb 15, 2019 at 19:48
  • Perhaps my answer here can help you: stackoverflow.com/questions/54490808/… Commented Feb 15, 2019 at 20:02
  • @Camilo Terevinto I do this because it's in my test project, when I prepare the text context. I can't use constructor injection here. So I don't agree it's 'completely wrong', but maybe you can explain how to do it properly then. Commented Feb 15, 2019 at 20:10
  • I think is better to use a UnitOfWorkFactory where you will create your UnitOfWork Commented Feb 15, 2019 at 20:15

2 Answers 2

1

No need to pass UnitOfWork argument such as above. Define your UnitOfWork structure in with configuration like this

            var mySqlConn = Configuration.GetSection("MySqlConnection").Get<MySqlConnection>();
            services.AddDbContext<MySQLContext>(options => options.UseMySql(mySqlConn.MySqlConnectionString));

            services.AddScoped(typeof(IRepository<,>), typeof(Repository<,>));
            services.AddTransient(typeof(IUnitOfWork<>), typeof(UnitOfWork<>));

Visual Studio will resolve it on runtime

Sign up to request clarification or add additional context in comments.

Comments

0

I found a solution.

I execute each test in its own text context, like (simplified example):

  using (var context = IntegrationTestEnvironment.Setup())
  {
      User bobbyUserDo = new User();
      Profile bobbyPatientDo = new Patient(bobbyUserDo, "[email protected]");
      var bobbyUserDb = await context.UserRepository.AddOrUpdateAsync(bobbyUserDo);
      bobbyUserDb.Should().NotBeNull();
      bobbyUserDb.Id.Should().BeGreaterThan(0);
      bobbyUserDb.Profiles.Should().HaveCount(1);
      bobbyUserDb.Profiles.First().Id.Should().BeGreaterThan(0);
  }

In my Setup method, I prepare the environment:

  public static IntegrationTestContext Setup(bool prefillWithTestData)
  {           
        var webHost = WebHost.CreateDefaultBuilder()                
            .UseStartup<Startup>()
            .Build();
        var serviceProvider = new DependencyResolverHelpercs(webHost);
  }

The Startup method contains the ConfigureServices method, where I configure all services that I need for the test:

  public void ConfigureServices(IServiceCollection services)
    {            
        // Scans assemblies and adds MediatR handlers, preprocessors, and postprocessors implementations to the container.            
        services.AddMediatR(typeof(Application.Logic.Queries.FindUserByEmailAddressHandler));

        var localDb = new LocalDb();
        services.AddSingleton<ILocalDb, LocalDb>(uow => localDb);
        services.AddSingleton<IUnitOfWork, UnitOfWork>(uow => new UnitOfWork(localDb.ConnectionString));
        services.AddSingleton<IUserRepository, UserRepository>();
    }

At that time, I create a LocalDb (which creates a local database), and after that I can simply pass the connection string to my unit of work.

The test can than run within a context where I have dependency injection configured correctly, and valid within that test. That's why I have used Singleton: the instances are the same withing the context of that specific test; and after the test everything gets disposed and the local database is deleted.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.