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?

L-Four
  • 13,345
  • 9
  • 65
  • 109
  • `var localDb = serviceProvider.GetService();` 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 – Camilo Terevinto Feb 15 '19 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... – NaDeR Star Feb 15 '19 at 19:48
  • Perhaps my answer here can help you: https://stackoverflow.com/questions/54490808/configure-connection-string-from-controller-asp-net-core-mvc-2-1/54497955#54497955 –  Feb 15 '19 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. – L-Four Feb 15 '19 at 20:10
  • I think is better to use a UnitOfWorkFactory where you will create your UnitOfWork – Sergey K Feb 15 '19 at 20:15

2 Answers2

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

Hamit YILDIRIM
  • 4,224
  • 1
  • 32
  • 35
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, "bobbyPatient@hotmail.com");
      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.

L-Four
  • 13,345
  • 9
  • 65
  • 109