Table of Contents

Injecting services into components under test

It is common for components under test to have a dependency on services, injected into them through the @inject IMyService MyService syntax in .razor files, or the [Inject] private IMyService MyService { get; set; } syntax in .cs files.

This is supported in bUnit through the Services collection available through the test context. The Services collection is just an IServiceCollection, which means services can be registered in the same manner as done for production code in Startup.cs in Blazor Server projects and in Program.cs in Blazor WASM projects.

In bUnit, you register the services in the Services collection before you render a component under test.

Note

The AddSingleton() method is only available on the Services collection if you import the Microsoft.Extensions.DependencyInjection namespace in your test class.

The following sections demonstrate how to do this. The examples we will cover will test the <WeatherForecasts> component listed below, which depends on the IWeatherForecastService service, injected in line 1:

@inject IWeatherForecastService ForecastService

<h1>Weather forecast</h1>

<p>This component demonstrates fetching data from a service.</p>

@if (Forecasts is null)
{
  <p><em>Loading...</em></p>
}
else
{
  <WeatherForecastTabel Forecasts=@Forecasts />
}

@code 
{
  public WeatherForecast[] Forecasts { get; private set; }

  protected override async Task OnInitializedAsync()
  {
    Forecasts = await ForecastService.GetForecastAsync(DateTime.Now);
  }
}

Injecting services in tests

Here is a test that registers the IWeatherForecastService in the Services collection, which is a requirement of the <WeatherForecasts> component listed above.

  Services.AddSingleton<IWeatherForecastService>(new WeatherForecastService());

  // RenderComponent will inject the service in the WeatherForecasts component
  // when it is instantiated and rendered.
  var cut = RenderComponent<WeatherForecasts>();

  // Assert that service is injected
  Assert.NotNull(cut.Instance.Forecasts);
}

The highlighted line shows how the IWeatherForecastService is registered in the test context's Services collection, which is just a standard IServiceCollection, using the standard .NET Core dependency injection (DI) services method, AddSingleton.

Fallback service provider

A fallback service provider can be registered with the built-in TestServiceProvider. This enables a few interesting use cases, such as using an alternative IoC container (which should implement the IServiceProvider interface), or automatically creating mock services for your Blazor components. The latter can be achieved by using a combination of AutoFixture and your favorite mocking framework, e.g. Moq, NSubsitute, or Telerik JustMock.

When is the fallback service provider used?

The logic inside the TestServiceProvider for using the fallback service provider is as follows:

  1. Try resolving the requested service from the standard service provider in bUnit.
  2. If that fails, try resolving from a fallback service provider, if one exists.

In other words, the fallback service provider will always be tried after the default service provider has had a chance to fulfill a request for a service.

Registering a fallback service provider

This is an example of how to implement and use a fallback service provider:

public class FallbackServiceProvider : IServiceProvider
{
  public object GetService(Type serviceType)
  {
    return new DummyService();
  }
}

public class DummyService

Here is a test where the fallback service provider is used:

Services.AddFallbackServiceProvider(new FallbackServiceProvider());

var dummyService = Services.GetService<DummyService>();

Assert.NotNull(dummyService);

In this example, the DummyService is provided by the fallback service provider, since it is not registered in the default service provider.

Using a custom IServiceProvider implementation

A custom service provider factory can be registered with the built-in TestServiceProvider. It is used to create the underlying IServiceProvider. This enables a few interesting use cases, such as using an alternative IoC container (which should implement the IServiceProvider interface). This approach can be useful if the fallback service provider is not an option. For example, if you have dependencies in the fallback container, that rely on dependencies which are in the main container and vice versa.

Registering Autofac service provider factory

The example makes use of AutofacServiceProviderFactory and AutofacServiceProvider from the package Autofac.Extensions.DependencyInjection and shows how to use an Autofac dependency container with bUnit.

Here is a test where the Autofac service provider factory is used:

[Fact]
public void AutofacServiceProviderViaFactoryReturns()
{
  void ConfigureContainer(ContainerBuilder containerBuilder)
  {
    containerBuilder
      .RegisterType<DummyService>()
      .AsSelf();
  }

  Services.UseServiceProviderFactory(new AutofacServiceProviderFactory(ConfigureContainer));

  //get a service which was installed in the Autofac ContainerBuilder

  var dummyService = Services.GetService<DummyService>();

  Assert.NotNull(dummyService);

  //get a service which was installed in the bUnit ServiceCollection

  var testContextBase = Services.GetService<TestContextBase>();

  Assert.NotNull(testContextBase);
  Assert.Equal(this, testContextBase);
}

Here is a test where the Autofac service provider is used via delegate:

[Fact]
public void AutofacServiceProviderViaDelegateReturns()
{
  ILifetimeScope ConfigureContainer(IServiceCollection services)
  {
    var containerBuilder = new ContainerBuilder();

    containerBuilder
      .RegisterType<DummyService>()
      .AsSelf();

    containerBuilder.Populate(services);

    return containerBuilder.Build();
  }

  Services.UseServiceProviderFactory(x => new AutofacServiceProvider(ConfigureContainer(x)));

  //get a service which was installed in the Autofac ContainerBuilder

  var dummyService = Services.GetService<DummyService>();

  Assert.NotNull(dummyService);

  //get a service which was installed in the bUnit ServiceCollection

  var testContextBase = Services.GetService<TestContextBase>();

  Assert.NotNull(testContextBase);
  Assert.Equal(this, testContextBase);
}

Registering a custom service provider factory

The examples contain dummy implementations of IServiceProvider and IServiceProviderFactory<TContainerBuilder>. Normally those implementations are supplied by the creator of your custom dependency injection solution (e.g. Autofac example above). This dummy implementations are not intended to use as is.

This is an example of how to implement and use a dummy custom service provider factory.

public sealed class CustomServiceProvider : IServiceProvider, IServiceScopeFactory, IServiceScope
{
  private readonly IServiceProvider _serviceProvider;

  public CustomServiceProvider(IServiceCollection serviceDescriptors)
    => _serviceProvider = serviceDescriptors.BuildServiceProvider();

  public object GetService(Type serviceType)
  {
    if (serviceType == typeof(IServiceScope) || serviceType == typeof(IServiceScopeFactory))
      return this;

    if (serviceType == typeof(DummyService))
      return new DummyService();

    return _serviceProvider.GetService(serviceType);
  }

  void IDisposable.Dispose() { }
  public IServiceScope CreateScope() => this;
  IServiceProvider IServiceScope.ServiceProvider => this;
}

public sealed class CustomServiceProviderFactoryContainerBuilder
{
  private readonly IServiceCollection _serviceDescriptors;

  public CustomServiceProviderFactoryContainerBuilder(IServiceCollection serviceDescriptors)
    => this._serviceDescriptors = serviceDescriptors;

  public IServiceProvider Build()
    => new CustomServiceProvider(_serviceDescriptors);
}

public sealed class CustomServiceProviderFactory : IServiceProviderFactory<CustomServiceProviderFactoryContainerBuilder>
{
  public CustomServiceProviderFactoryContainerBuilder CreateBuilder(IServiceCollection services)
    => new CustomServiceProviderFactoryContainerBuilder(services);

  public IServiceProvider CreateServiceProvider(CustomServiceProviderFactoryContainerBuilder containerBuilder)
    => containerBuilder.Build();
}

Here is a test where the custom service provider factory is used:

Services.UseServiceProviderFactory(new CustomServiceProviderFactory());

var dummyService = Services.GetService<DummyService>();

Assert.NotNull(dummyService);

Here is a test where the custom service provider is used via delegate:

Services.UseServiceProviderFactory(x => new CustomServiceProvider(x));

var dummyService = Services.GetService<DummyService>();

Assert.NotNull(dummyService);

Using libraries like AutoMocker as fallback provider

It is possible to use libraries that automatically create mock services as fallback service providers. Here is an example implementation that utilizes the AutoMocker

public class MockServiceProvider : IServiceProvider
{
    private readonly AutoMocker _autoMocker = new AutoMocker();

    public object? GetService(Type serviceType)
    {
        return _autoMocker.Get(serviceType);
    }
}
Warning

An exception has to be made for IComponentActivator, if "Loose" mode is used. Otherwise, runtime exception can be thrown.

public class MockServiceProvider : IServiceProvider
{
    private readonly AutoMocker _autoMocker = new AutoMocker(MockBehavior.Loose);

    public object? GetService(Type serviceType)
    {
        if (serviceType == typeof(IComponentActivator))
        {
            return null;
        }

        return _autoMocker.Get(serviceType);
    }
}

Further reading

A closely related topic is mocking. To learn more about mocking in bUnit, go to the Mocking and faking component dependencies page.

Progress Telerik

Premium sponsor: Progress Telerik.

Packt

Editorial support provided by Packt.

.NET Foundation

Supported by the .NET Foundation.