When your test project references Microsoft.Extensions.DependencyInjection.Abstractions version 8.x or newer, this generator creates WithKeyedService<T>() extension methods for AutoMocker that enable testing classes that depend on keyed services via IKeyedServiceProvider.
- Automatically generates when
Microsoft.Extensions.DependencyInjectionis referenced - Supports registering services with specific keys
- Provides both eager and lazy service registration
- Automatically resolves
[FromKeyedServices]attribute parameters - Integrates seamlessly with
IServiceProviderandIKeyedServiceProvider
Keyed services are a feature in .NET's dependency injection that allows you to register multiple implementations of the same interface and distinguish them using keys. This is useful when you need different implementations of the same service type in different contexts.
Register a service instance with a specific key:
using Microsoft.Extensions.DependencyInjection;
public class MyService
{
public IEmailSender EmailSender { get; }
public MyService([FromKeyedServices("primary")] IEmailSender emailSender)
{
EmailSender = emailSender;
}
}
[TestClass]
public class MyServiceTests
{
[TestMethod]
public void Test_WithKeyedService()
{
AutoMocker mocker = new();
// Register a keyed service
IEmailSender primarySender = Mock.Of<IEmailSender>();
mocker.WithKeyedService(primarySender, "primary");
MyService service = mocker.CreateInstance<MyService>();
Assert.AreEqual(primarySender, service.EmailSender);
}
}Register multiple services with different keys:
public class NotificationService
{
public IEmailSender PrimarySender { get; }
public IEmailSender BackupSender { get; }
public NotificationService(
[FromKeyedServices("primary")] IEmailSender primarySender,
[FromKeyedServices("backup")] IEmailSender backupSender)
{
PrimarySender = primarySender;
BackupSender = backupSender;
}
}
[TestMethod]
public void Test_MultipleKeyedServices()
{
AutoMocker mocker = new();
// Register multiple keyed services
IEmailSender primary = Mock.Of<IEmailSender>();
IEmailSender backup = Mock.Of<IEmailSender>();
mocker.WithKeyedService(primary, "primary");
mocker.WithKeyedService(backup, "backup");
NotificationService service = mocker.CreateInstance<NotificationService>();
Assert.AreEqual(primary, service.PrimarySender);
Assert.AreEqual(backup, service.BackupSender);
}Access keyed services through IKeyedServiceProvider:
public class ServiceConsumer
{
public IService Service { get; }
public ServiceConsumer(IServiceProvider serviceProvider)
{
Service = serviceProvider.GetRequiredKeyedService<IService>("my-key");
}
}
[TestMethod]
public void Test_IKeyedServiceProvider()
{
AutoMocker mocker = new();
IService myService = Mock.Of<IService>();
mocker.WithKeyedService(myService, "my-key");
ServiceConsumer consumer = mocker.CreateInstance<ServiceConsumer>();
Assert.AreEqual(myService, consumer.Service);
}Register a keyed service that will be created by AutoMocker when first accessed:
public interface ICache { }
public class RedisCache : ICache { }
public class MyService
{
public ICache Cache { get; }
public MyService([FromKeyedServices("redis")] ICache cache)
{
Cache = cache;
}
}
[TestMethod]
public void Test_LazyKeyedService()
{
AutoMocker mocker = new();
// Register a keyed service that will be created lazily
mocker.WithKeyedService<ICache, RedisCache>("redis");
MyService service = mocker.CreateInstance<MyService>();
Assert.IsNotNull(service.Cache);
Assert.IsInstanceOfType(service.Cache, typeof(RedisCache));
}The generator creates the following extension methods:
public static void WithKeyedService<TService>(this AutoMocker mocker, TService service, object? key)
where TService : class
{
// Registers the provided service instance with the specified key
}
public static void WithKeyedService<TService, TImplementation>(this AutoMocker mocker, TImplementation service, object? key)
where TImplementation : class, TService
{
// Registers the provided service instance with the specified key and service type
}public static void WithKeyedService<TService>(this AutoMocker mocker, object? key)
where TService : class
{
// Registers a keyed service that will be created using AutoMocker when requested
}
public static void WithKeyedService<TService, TImplementation>(this AutoMocker mocker, object? key)
where TImplementation : class, TService
{
// Registers a keyed service that will be created using AutoMocker when requested
}The generator:
- Creates a
ServiceProviderResolverthat intercepts dependency resolution - Implements both
IServiceProviderandIKeyedServiceProviderinterfaces - Automatically detects
[FromKeyedServices]attributes on constructor parameters - Resolves keyed services from a centralized registry
- Falls back to AutoMocker's default resolution for non-keyed services
public interface IRepository { }
public class SqlRepository : IRepository { }
public class MongoRepository : IRepository { }
[TestMethod]
public void Test_DifferentImplementations()
{
AutoMocker mocker = new();
// Register different implementations for the same interface
mocker.WithKeyedService<IRepository, SqlRepository>("sql");
mocker.WithKeyedService<IRepository, MongoRepository>("mongo");
var sqlRepo = mocker.Get<IServiceProvider>()
.GetRequiredKeyedService<IRepository>("sql");
var mongoRepo = mocker.Get<IServiceProvider>()
.GetRequiredKeyedService<IRepository>("mongo");
Assert.IsInstanceOfType(sqlRepo, typeof(SqlRepository));
Assert.IsInstanceOfType(mongoRepo, typeof(MongoRepository));
}Lazy-registered keyed services can have their own dependencies resolved by AutoMocker:
public class CacheService : ICache
{
public ILogger<CacheService> Logger { get; }
public CacheService(ILogger<CacheService> logger)
{
Logger = logger;
}
}
[TestMethod]
public void Test_KeyedServiceWithDependencies()
{
AutoMocker mocker = new();
// The CacheService will be created with its logger dependency automatically mocked
mocker.WithKeyedService<ICache, CacheService>("primary");
var cache = mocker.Get<IServiceProvider>()
.GetRequiredKeyedService<ICache>("primary");
Assert.IsNotNull(cache);
Assert.IsInstanceOfType(cache, typeof(CacheService));
// Dependencies are automatically resolved
var cacheService = (CacheService)cache;
Assert.IsNotNull(cacheService.Logger);
}You can use null as a service key:
[TestMethod]
public void Test_NullKey()
{
AutoMocker mocker = new();
IService service = Mock.Of<IService>();
mocker.WithKeyedService(service, null);
var resolved = mocker.Get<IServiceProvider>()
.GetKeyedService<IService>(null);
Assert.AreEqual(service, resolved);
}You can disable this generator using an MSBuild property in your test project's .csproj file:
<PropertyGroup>
<EnableMoqAutoMockerKeyedServicesGenerator>false</EnableMoqAutoMockerKeyedServicesGenerator>
</PropertyGroup><Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<!-- Disable Keyed Services Extension Generator -->
<EnableMoqAutoMockerKeyedServicesGenerator>false</EnableMoqAutoMockerKeyedServicesGenerator>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Moq.AutoMock" Version="3.5.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
</ItemGroup>
</Project>- Verify
Microsoft.Extensions.DependencyInjectionis referenced in your test project - Check that the generator is not disabled in your
.csproj - Rebuild the project to trigger generator execution
- Ensure you're using the
Moq.AutoMocknamespace
- Make sure you're calling
WithKeyedService()before creating your instance - Verify the service key matches exactly (including type and value)
- Check that the constructor parameter has the
[FromKeyedServices]attribute - Ensure you're accessing via
IKeyedServiceProvideror[FromKeyedServices]parameter
If you get an "Failed to resolve keyed service" exception:
- Verify the service was registered with the correct key
- Check the service type matches the requested type
- For lazy registration, ensure the implementation type can be created by AutoMocker
- Confirm you're using
GetRequiredKeyedServiceorGetKeyedServicewith the right key
Consider using constants or enums for service keys to avoid typos:
public static class ServiceKeys
{
public const string Primary = "primary";
public const string Backup = "backup";
public const string Cache = "cache";
}
[TestMethod]
public void Test_WithConstants()
{
AutoMocker mocker = new();
mocker.WithKeyedService(Mock.Of<IService>(), ServiceKeys.Primary);
// Use the same constant when accessing
var service = mocker.Get<IServiceProvider>()
.GetRequiredKeyedService<IService>(ServiceKeys.Primary);
}Use lazy registration when the service has dependencies:
// Prefer this - dependencies are automatically resolved
mocker.WithKeyedService<ICache, CacheService>("cache");
// Over this - you have to manually set up dependencies
var cache = new CacheService(Mock.Of<ILogger<CacheService>>());
mocker.WithKeyedService<ICache>(cache, "cache");Keyed services work well with other generator features:
[TestMethod]
public void Test_CombinedGenerators()
{
AutoMocker mocker = new();
mocker.WithFakeLogging()
.WithOptions<MySettings>(s => s.Timeout = 30);
mocker.WithKeyedService<ICache, RedisCache>("redis");
var service = mocker.CreateInstance<MyComplexService>();
// All dependencies including keyed services are resolved
}