Is it possible to use Dependency Injection with xUnit?

Unit TestingTestingDependency InjectionIoc ContainerXunit

Unit Testing Problem Overview


I have a test class with a constructor that needs an IService.

public class ConsumerTests
{
    private readonly IService _service;
    public ConsumerTests(IService servie)
    {
      _service = service;
    }
  
    [Fact]
    public void Should_()
    {
       //use _service
    }
}

I want to plugin my DI container of choice to build the test class.

Is this possible with xUnit?

Unit Testing Solutions


Solution 1 - Unit Testing

Yes it's possible with Xunit.DependencyInjection

Install-Package Xunit.DependencyInjection

and you can inject your services

[assembly: TestFramework("Your.Test.Project.Startup", "AssemblyName")]
    
namespace Your.Test.Project
{
    public class Startup : DependencyInjectionTestFramework
    {
        public Startup(IMessageSink messageSink) : base(messageSink) { }
    
        protected override void ConfigureServices(IServiceCollection services)
        {
            services.AddTransient<IDependency, DependencyClass>();
        }
    }
}

https://github.com/pengweiqhca/Xunit.DependencyInjection

Solution 2 - Unit Testing

Yes there is now, these two questions and answers should be consolidated in my opinion, see answer here

https://stackoverflow.com/questions/57331395/net-core-execute-all-dependency-injection-in-xunit-test-for-appservice-reposit/57387120#57387120

Use Custom Web Application Factory and ServiceProvider.GetRequiredService below, feel free to edit and optimize answer

CustomWebApplicationFactory:

public class CustomWebApplicationFactory<TStartup> : WebApplicationFactory<TStartup> where TStartup : class
{
    protected override void ConfigureWebHost(IWebHostBuilder builder)
    {
        builder.ConfigureAppConfiguration((hostingContext, configurationBuilder) =>
        {
            var type = typeof(TStartup);
            var path = @"C:\\OriginalApplication";
    
            configurationBuilder.AddJsonFile($"{path}\\appsettings.json", optional: true, reloadOnChange: true);
            configurationBuilder.AddEnvironmentVariables();
        });
    
        // if you want to override Physical database with in-memory database
        builder.ConfigureServices(services =>
        {
            var serviceProvider = new ServiceCollection()
                .AddEntityFrameworkInMemoryDatabase()
                .BuildServiceProvider();
    
            services.AddDbContext<ApplicationDBContext>(options =>
            {
                options.UseInMemoryDatabase("DBInMemoryTest");
                options.UseInternalServiceProvider(serviceProvider);
            });
        });
    }
}

Integration Test:

public class DepartmentAppServiceTest : IClassFixture<CustomWebApplicationFactory<OriginalApplication.Startup>>
{
    public CustomWebApplicationFactory<OriginalApplication.Startup> _factory;
    public DepartmentAppServiceTest(CustomWebApplicationFactory<OriginalApplication.Startup> factory)
    {
        _factory = factory;
        _factory.CreateClient();
    }

    [Fact]
    public async Task ValidateDepartmentAppService()
    {      
        using (var scope = _factory.Server.Host.Services.CreateScope())
        {
            var departmentAppService = scope.ServiceProvider.GetRequiredService<IDepartmentAppService>();
            var dbtest = scope.ServiceProvider.GetRequiredService<ApplicationDBContext>();
            dbtest.Department.Add(new Department { DepartmentId = 2, DepartmentCode = "123", DepartmentName = "ABC" });
            dbtest.SaveChanges();
            var departmentDto = await departmentAppService.GetDepartmentById(2);
            Assert.Equal("123", departmentDto.DepartmentCode);
        }
    }
}

Resources:

https://docs.microsoft.com/en-us/aspnet/core/test/integration-tests?view=aspnetcore-2.2

https://fullstackmark.com/post/20/painless-integration-testing-with-aspnet-core-web-api

Solution 3 - Unit Testing

There is a way to do this using nuget package out of this source code: https://github.com/dennisroche/xunit.ioc.autofac

It works great as long as you use [Fact], but then I got blocked when started using [Theory]. There is a pull request to sort this out.

To unblock myself, I used CollectionFixture to inject Container and from the container, I resolve the Interface.

Solution 4 - Unit Testing

What are you trying to test? The implementation of IService or the wiring of the DI container?

If you are testing IService implementations, you should be instantiating them directly in the test (and mocking any dependencies):

var service = new MyServiceImplementation(mockDependency1, mockDependency2, ...);
// execute service and do your asserts, probably checking mocks

If you are trying to test the wiring of the DI container, you need to reach out and grab the configured container explicitly. There is no "composition root" that will do that for you (pseudo-code follows, kind of Autofac flavored):

var myContainer = myCompositionRoot.GetContainer();
var service = myContainer.ResolveCompnent<IService>();
// execute service and do your asserts with the actual implementation

If you are using xUnit for running integration tests where you need to use the same object in multiple tests, look at Fixtures: https://xunit.net/docs/shared-context.

Solution 5 - Unit Testing

There are other answers that are better for your question, but I wanted to show how we could do it without an IOC container using TheoryData.

Watch this: Here's my interface

public interface IEpisodeManager
{
    Task<bool> Update(Episode episode);
    Task<bool> Set(IDictionary<string, IList<Episode>> creatorsToEpisodes);
    Task<Episode> GetById(string creatorId, int episodeId);
    Task<IEnumerable<Episode>> GetEpisodesByCreatorId(string creatorId);
}

Stay tuned, this is the test class:

  1. What we're doing here is using the interface as the input parameter to the test.

  2. Then creating a TheoryData object. This can be initialized just like a list. If you have dependencies and don't want nested constructor calls, just wrap this in a property.

  3. Place a Theory and MemberData attribute on your test methods.

    public class EpisodeManagerTests { public static TheoryData EpisodeManager = new TheoryData() { new CreatorToKeysEpisodeManager(), new CreatorToEpisodeManager() };

     public EpisodeManagerTests()
     {
     }
    
     [Theory]
     [MemberData(nameof(EpisodeManager), MemberType = typeof(EpisodeManagerTests))]
     public async Task GetById_ResponseProperlyMapped(IEpisodeManager manager)
     {
         var dict = EpisodeMock.CreatorToEpisodes;
         var setResult = await manager.Set(dict);
         Assert.True(setResult);
    
         var getResult = await manager.GetById("creator1", 2);
         Assert.NotNull(getResult);
     }
    
     [Theory]
     [MemberData(nameof(EpisodeManager), MemberType = typeof(EpisodeManagerTests))]
     public async Task GetEpisodesByCreatorId_ResponseProperSize(IEpisodeManager manager)
     {
         var dict = EpisodeMock.CreatorToEpisodes;
         var setResult = await manager.Set(dict);
         Assert.True(setResult);
    
         var getResult = await manager.GetEpisodesByCreatorId("creator1");
         Assert.True(getResult.Count() == 4);
     }
    
     [Theory]
     [MemberData(nameof(EpisodeManager), MemberType = typeof(EpisodeManagerTests))]
     public async Task GetEpisodesId_ResponseProperlyMapped(IEpisodeManager manager)
     {
         var dict = EpisodeMock.CreatorToEpisodes;
         var setResult = await manager.Set(dict);
         Assert.True(setResult);
    
         var getResult = await manager.GetById("creator1", 1);
         Assert.Equal(1, getResult.Id);
         Assert.Equal("creator1", getResult.CreatorId);
         Assert.Equal("filepath", getResult.FilePath);
         Assert.Equal("Casablanca", getResult.Name);
         Assert.Equal<TimeSpan>(TimeSpan.FromHours(4.99), getResult.RunningTime);
         Assert.Equal(DateTime.Parse("6/25/21"), getResult.AiredDate);
     }
    
     [Theory]
     [MemberData(nameof(EpisodeManager), MemberType = typeof(EpisodeManagerTests))]
     public async Task Update_ResponseProperlyMapped(IEpisodeManager manager)
     {
         var dict = EpisodeMock.CreatorToEpisodes;
         var setResult = await manager.Set(dict);
         Assert.True(setResult);
    
         var updateRequest = new Episode()
         {
             AiredDate = DateTime.Parse("6/25/21"),
             CreatorId = "creator1",
             FilePath = "filepath",
             Id = 1,
             Name = "Casablanca: The origin story",
             RunningTime = TimeSpan.FromHours(4.99)
         };
    
         var updateResult = await manager.Update(updateRequest);
    
         var getResult = await manager.GetById("creator1", 1);
         Assert.Equal(1, getResult.Id);
         Assert.Equal("creator1", getResult.CreatorId);
         Assert.Equal("filepath", getResult.FilePath);
         Assert.Equal("Casablanca: The origin story", getResult.Name);
         Assert.Equal<TimeSpan>(TimeSpan.FromHours(4.99), getResult.RunningTime);
         Assert.Equal(DateTime.Parse("6/25/21"), getResult.AiredDate);
     }
    

    }

This will write the tests for various implementations of your interface.

This likely isn't practical if you have several layers of dependencies, but if it's simple enough of a project, this is a super easy way to go.

Solution 6 - Unit Testing

This library (developed by me) (Xunit.Microsoft.DependencyInjection) supports .NET 6.0 and brings in Microsoft's dependency injection container to Xunit by leveraging Xunit's fixture.

Getting started

Nuget package

First add the following nuget package to your Xunit project:

Install-Package Xunit.Microsoft.DependencyInjection
Setup your fixture

The abstract class of Xunit.Microsoft.DependencyInjection.Abstracts.TestBedFixture contains the necessary functionalities to add services and configurations to Microsoft's dependency injection container. Your concrete test fixture class must derive from this abstract class and implement the following two abstract methods:

protected abstract IEnumerable<string> GetConfigurationFiles();
protected abstract void AddServices(IServiceCollection services, IConfiguration configuration);

GetConfigurationFiles(...) method returns a collection of the configuration files in your Xunit test project to the framework. AddServices(...) method must be used to wire up the implemented services.

Access the wired up services

There are two method that you can use to access the wired up service depending on your context:

public T GetScopedService<T>(ITestOutputHelper testOutputHelper);
public T GetService<T>(ITestOutputHelper testOutputHelper);
Adding custom logging provider

Test developers can add their own desired logger provider by overriding AddLoggingProvider(...) virtual method defined in TestBedFixture class.

Preparing Xunit test classes

Your Xunit test class must be derived from Xunit.Microsoft.DependencyInjection.Abstracts.TestBed<T> class where T should be your fixture class derived from TestBedFixture.

Also, the test class should be decorated by the following attribute:

[CollectionDefinition("Dependency Injection")]

Running tests in order

The library also has a bonus feature that simplifies running tests in order. The test class does not have to be derived from TestBed<T> class though and it can apply to all Xunit classes.

Decorate your Xunit test class with the following attribute and associate TestOrder(...) with Fact and Theory:

[TestCaseOrderer("Xunit.Microsoft.DependencyInjection.TestsOrder.TestPriorityOrderer", "Xunit.Microsoft.DependencyInjection")]

Examples

  • Please follow this link to view a couple of examples on utilizing this library.
  • Digital Silo's unit tests and integration tests are using this library.
One more thing

Do not forget to include the following nuget packages to your Xunit project:

  • Microsoft.Extensions.DependencyInjection

  • Microsoft.Extensions.Configuration

  • Microsoft.Extensions.Options

  • Microsoft.Extensions.Configuration.Binder

  • Microsoft.Extensions.Configuration.FileExtensions

  • Microsoft.Extensions.Configuration.Json

  • Microsoft.Extensions.Logging

Solution 7 - Unit Testing

xunit.di is an extension of xUnit testing framework, built to support xUnit dependency injection, which allows us to achieve Inversion of Control (IoC) between test classes and their dependencies.

Install-Package Xunit.Di

To use xunit.di:

  • Install the xunit.di nuget package
  • Create a Setup.cs class, (optional) and inherits the Xunit.Di.Setup.cs
  • Configure dependencies in the Setup.cs class.

Find instructions from xunit.di GET-STARTED

You need a Setup.cs class which configures IServiceProvider,

    public class Setup
    {
        private readonly IHostBuilder _defaultBuilder;
        private IServiceProvider _services;
        private bool _built = false;

        public Setup()
        {
            _defaultBuilder = Host.CreateDefaultBuilder();
        }

        public IServiceProvider Services => _services ?? Build();

        private IServiceProvider Build()
        {
            if (_built)
                throw new InvalidOperationException("Build can only be called once.");
            _built = true;

            _defaultBuilder.ConfigureServices((context, services) =>
            {
                services.AddSingleton<IService, ServiceImpl>();
                // where ServiceImpl implements IService
                // ... add other services when needed
            });

            _services = _defaultBuilder.Build().Services;
            return _services;
        }
    }

And your test class looks like below,

    public class ConsumerTests
    {
        private readonly IService _service;
        public ConsumerTests(IService servie)
        {
            _service = service;
        }

        [Fact]
        public void Should_()
        {
            var result = _service.GetById("1");
            Assert.NotNull(result);
            //use _service
        }
    }

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionRookianView Question on Stackoverflow
Solution 1 - Unit TestingHatimView Answer on Stackoverflow
Solution 2 - Unit Testinguser11860043View Answer on Stackoverflow
Solution 3 - Unit TestingSiva KandarajView Answer on Stackoverflow
Solution 4 - Unit TestingColin YoungView Answer on Stackoverflow
Solution 5 - Unit TestingmattylantzView Answer on Stackoverflow
Solution 6 - Unit TestingArashView Answer on Stackoverflow
Solution 7 - Unit TestingcliView Answer on Stackoverflow