Mocking a Service using Moq in C# - c#

I have a service that is setup in this way.
public Interface IDataService : IDisposable
{
IQueryable<T> Set<T>() where T : class;
IDbSet<T> WritableSet<T>() where T : class;
}
IDataService is inherited by DataService.
public abstract class DataService : IDataService
{
public IDataContext DataContext { get; private set; }
public IQueryable<T> Set<T>() where T : class
{
return DataContext.Set<T>().AsNoTracking();
}
public IDbSet<T> WritableSet<T>() where T : class
{
return DataContext.Set<T>();
}
public AddResult<T> Add<T>(T obj) where T : class, IPersistentEntity
{
if (obj == null)
return new AddResult<T>() { IsValid = false };
else
{
if (obj.Id == Guid.Empty)
WritableSet<T>().Add(obj);
bool success = DataContext.SaveChanges() > 0;
return new AddResult<T>() { Entity = obj, IsValid = success };
}
}
}
And The DataService is inherited by EntityService.
public class EntityService : DataService
{
public EntityService(IDataContext DataContext) : base(DataContext)
{
}
public void EntityStarted(Guid Id)
{
var a = GetWriteableById<Entity>(Id);
a.Status = 1;
DataContext.SaveChanges();
}
}
This EntityService is used in one of my components. EntityService's object is created and passed to the component's constructor.
I'm using Moq to perform some tests on the component and for that, the plan was to mock the EntityService such that the EntityService uses a fake db container with dummy data for database like operations. But, I'm not having the best idea to mock this with minimum amount of new code.
The least appealing idea that I have is to create a fake EntityService class using the interface and have it's own implementation suitable for tests.
Help is appreciated! :)

As per #JLe and #Chetan's comment on the question, I had to mock the DbContext.
I followed this article to mock the DbContext.
Mocking DbContext with Moq
Here is how the code looks like.
private void Setup()
{
List<Entity> entityData = new List<Entity>();
entityData.Add(new Entity
{
Id = Guid.NewGuid()
});
DbSet<Entity> MockEntitySet = GetSet(entityData);
MockContext = new Mock<IDbContext>();
MockContext.Setup(m => m.Set<Entity>()).Returns(MockEntitySet);
}
public static DbSet<T> GetSet<T>(List<T> sourceList) where T : class
{
return GetSet(sourceList.ToArray());
}
public static DbSet<T> GetSet<T>(T[] sourceList) where T : class
{
var name = typeof(T).Name;
var queryable = sourceList.AsQueryable();
Mock<DbSet<T>> dbSet = new Mock<DbSet<T>>();
dbSet.As<IQueryable<T>>().Setup(m => m.Provider).Returns(queryable.Provider);
dbSet.As<IQueryable<T>>().Setup(m => m.Expression).Returns(queryable.Expression);
dbSet.As<IQueryable<T>>().Setup(m => m.ElementType).Returns(queryable.ElementType);
dbSet.As<IQueryable<T>>().Setup(m => m.GetEnumerator()).Returns(queryable.GetEnumerator());
dbSet.Setup(m => m.AsNoTracking()).Returns(dbSet.Object);
return dbSet.Object;
}
[TestMethod]
public void Test()
{
EntityService service = new EntityService(MockContext.Object);
ComponentToTest compObj = new ComponentToTest(service);
compObj.MethodToTest(...);
// Assertions
}
Thank you guys! Appreciate your help and suggestions.

To mock you need an Interface, if not, you need to mark the methods that you want to mock as virtual.
Under the hood the mocking framework is going to create a new implementation for you that behaves as you configured the mock.
Hope it helps.

Related

Unit Testing Generic Methods (NUnit)

I have been trying to implement the repository pattern in .Net Core using a generic class. This is what i have come up with (ive taken out all but one method to keep it simple). The class / method works, but im trying to write a unit(integration) test for it, in this case the Add Method.
public class Repository<TEntity> : IRepository<TEntity> where TEntity : class
{
protected readonly DbContext Context;
public Repository(DbContext context)
{
Context = context;
}
public void Add(TEntity entity)
{
Context.Set<TEntity>().Add(entity);
}
}
So far my integration test is as follows,
Set Up makes a SqlLite database to save to
Sets up the DbContext
Sets up a Unit of Work - The unit of work is needed to SaveChanges the the DbContext and can be ignored for the most part
Then im creating a "StorageSystem" Domain Object and testing the Generic class. But it feels like I should be able to test it with out passing a specific domain model. Or at the very least, enter the different domain models as parameterised tests.
[TestFixture]
public class RepositoryTests
{
SqliteConnection _connection;
DbContextOptions<ApplicationDbContext> _options;
ApplicationDbContext _context;
UnitOfWork _uow;
[SetUp]
public void SetUp()
{
_connection = new SqliteConnection("DataSource=:memory:");
_connection.Open();
_options = new DbContextOptionsBuilder<ApplicationDbContext>()
.UseSqlite(_connection)
.Options;
using (var context = new ApplicationDbContext(_options))
{
context.Database.EnsureCreated();
}
_context = new ApplicationDbContext(_options);
_uow = new UnitOfWork(_context);
}
[TearDown]
public void TearDown()
{
_connection.Close();
}
[Test]
public void Add_AddsEntityToRepository()
{
//arrange
var storageSystem = new StorageSystem {Id = 1, Name = "Storage1"};
var repo = new Repository<StorageSystem>(_context);
//act
repo.Add(storageSystem);
_uow.Complete();
//assert
Assert.AreEqual(1, _context.StorageSystems.Count());
}
I am fairly new to working with Generics, The closest solution I could find was using an abstract class. However I couldnt make it working with me code with it not detecting the tests as an abstract class and not being able to make a repository of type TEntity.
Example taken from here
[TestFixture]
public abstract class RepositoryTests1<TEntity>
{
SqliteConnection _connection;
DbContextOptions<ApplicationDbContext> _options;
ApplicationDbContext _context;
UnitOfWork _uow;
[SetUp]
public void SetUp()
{
_connection = new SqliteConnection("DataSource=:memory:");
_connection.Open();
_options = new DbContextOptionsBuilder<ApplicationDbContext>()
.UseSqlite(_connection)
.Options;
using (var context = new ApplicationDbContext(_options))
{
context.Database.EnsureCreated();
}
_context = new ApplicationDbContext(_options);
_uow = new UnitOfWork(_context);
}
[TearDown]
public void TearDown()
{
_connection.Close();
}
[Test]
public void Add_AddsEntityToRepository_GenericAttempt()
{
//arrange
TEntity entityToAdd = this.CreateEntity();
var repo = new Repository<TEntity>(_context); //ERROR HERE - TEntity must be a reference type
//act
repo.Add(entityToAdd);
_uow.Complete();
//assert
//NO IDEA WHAT THE ASSERTION WOULD BE
}
protected abstract TEntity CreateEntity();
}
So in short, how do i unit test this Generic Repository?
You could Restrict your Repository to a certain base class created by you like EntityBase (this should be abstract)
public class EntityBase
{
public int Id { get; set; }
}
public class Repository<TEntity> : IRepository<TEntity> where TEntity : EntityBase
{
...
}
Then you are able to pass your base type to this test methods.
[Test]
public void Add_AddsEntityToRepository()
{
//arrange
var storageSystem = new StorageSystem {Id = 1, Name = "Storage1"};
var repo = new Repository<EntityBase>(_context);
//act
repo.Add(storageSystem);
_uow.Complete();
//assert
Assert.AreEqual(1, _context.StorageSystems.Count());
}
Since you are ensuring through inherintance, that every type has at least the members of EntityBase, you do not have to test further more down the inheritance tree of your entity types. (unless you have a certain use-case for that)
If you certain methods, that you need to know on the base-type, but the implementation depends on the child class, simply put the method as abstract member in the base class and override it in the child class.
public class EntityBase
{
public int Id { get; set; }
// sample method that copies member values from other object to current instance
public abstract void CopyProperties(EntityBase other);
}
public class Student : EntityBase
{
public int Id { get; set; }
public override void CopyProperties(EntityBase other)
{
...
}
}

Inject different DbContexts into generic repository based on Domain class - Autofac

In my application, I need to interact with two databases. I have two domain classes which are located in two different databases. I also have a generic repository pattern which accepts an UoW in its constructor. I am looking a way to inject appropriate UoW based on Domain class. I do not want to write second generic repository for the second database.. Is there any neat solution?
public interface IEntity
{
int Id { get; set; }
}
Located in Database A
public class Team: IEntity
{
public int Id { get; set; }
public string Name{ get; set; }
}
Located in Database B
public class Player: IEntity
{
public int Id { get; set; }
public string FullName { get; set; }
}
I also have a generic repository pattern with UoW
public interface IUnitOfWork
{
IList<IEntity> Set<T>();
void SaveChanges();
}
public class DbADbContext : IUnitOfWork
{
public IList<IEntity> Set<T>()
{
return new IEntity[] { new User() { Id = 10, FullName = "Eric Cantona" } };
}
public void SaveChanges()
{
}
}
public class DbBDataContext: IUnitOfWork
{
public IList<IEntity> Set<T>()
{
return new IEntity[] { new Tender() { Id = 1, Title = "Manchester United" } };
}
public void SaveChanges()
{
}
public interface IRepository<TEntity> where TEntity: class, IEntity
{
IList<IEntity> Table();
}
public class BaseRepository<TEntity> : IRepository<TEntity> where TEntity : class, IEntity
{
protected readonly IUnitOfWork Context;
public BaseRepository(IUnitOfWork context)
{
Context = context;
}
IList<IEntity> IRepository<TEntity>.Table()
{
return Context.Set<TEntity>();
}
}
I've already found articles saying that Autofac overrides the registration with the last value. I know my problem is how DbContexts are registered.
var builder = new ContainerBuilder();
// problem is here
builder.RegisterType<DbADbContext >().As<IUnitOfWork>()
builder.RegisterType<DbBDbContext >().As<IUnitOfWork>()
builder.RegisterGeneric(typeof(BaseRepository<>)).As(typeof(IRepository<>));
var container = builder.Build();
I inspired from #tdragon's answer.
The first step is registering Named DbContext
builder.RegisterType<Database1>()
.Keyed<IUnitOfWork>(DbName.Db1)
.Keyed<DbContext>(DbName.Db1).AsSelf().InstancePerRequest();
builder.RegisterType<Database2>()
.Keyed<IUnitOfWork>(DbName.Db2)
.Keyed<DbContext>(DbName.Db2).AsSelf().InstancePerRequest();
Please note that DbName is just an enum.
The following code scans the data access layer assembly for finding Domain classes. Then, it registers ReadOnlyRepository and BaseRepository. the place of this code is in DIConfig
Type entityType = typeof(IEntity);
var entityTypes = Assembly.GetAssembly(typeof(IEntity))
.DefinedTypes.Where(t => t.ImplementedInterfaces.Contains(entityType));
var baseRepoType = typeof(BaseRepository<>);
var readOnlyRepoType = typeof(ReadOnlyRepository<>);
var baseRepoInterfaceType = typeof(IRepository<>);
var readOnlyRepoInterfaceType = typeof(IReadOnlyRepository<>);
var dbContextResolver = typeof(DbContextResolverHelper).GetMethod("ResolveDbContext");
foreach (var domainType in entityTypes)
{
var baseRepositoryMaker = baseRepoType.MakeGenericType(domainType);
var readonlyRepositoryMarker = readOnlyRepoType.MakeGenericType(domainType);
var registerAsForBaseRepositoryTypes = baseRepoInterfaceType.MakeGenericType(domainType);
var registerAsForReadOnlyRepositoryTypes = readOnlyRepoInterfaceType.MakeGenericType(domainType);
var dbResolver = dbContextResolver.MakeGenericMethod(domainType);
// register BaseRepository
builder.Register(c => Activator.CreateInstance(baseRepositoryMaker, dbResolver.Invoke(null, new object[] { c }))
).As(registerAsForBaseRepositoryTypes).InstancePerRequest(jobTag);
//register readonly repositories
builder.Register(c => Activator.CreateInstance(readonlyRepositoryMarker, dbResolver.Invoke(null, new object[] { c })))
.As(registerAsForReadOnlyRepositoryTypes).InstancePerRequest(jobTag);
}
The following methods try to find DbSet in each DbContext in order to find out the Domain Classes belongs to which DataContext/Database.
public class DbContextResolverHelper
{
private static readonly ConcurrentDictionary<Type, DbName> TypeDictionary = new ConcurrentDictionary<Type, DbName>();
public static DbContext ResolveDbContext<TEntity>(IComponentContext c) where TEntity : class, IEntity
{
var type = typeof(DbSet<TEntity>);
var dbName = TypeDictionary.GetOrAdd(type, t =>
{
var typeOfDatabase1 = typeof(Database1);
var entityInDatabase1 = typeOfDatabase1 .GetProperties().FirstOrDefault(p => p.PropertyType == type);
return entityInDatabase1 != null ? DbName.Db1: DbName.Db2;
});
return c.ResolveKeyed<DbContext>(dbName);
}
}
What about this:
builder.RegisterType<DbContextBase>().As<IUnitOfWork>()
And
DbADataContext: DbContextBase,IUnitOfWork
DbBDataContext: DbContextBase,IUnitOfWork
Or in your registration you can just do something like :
containerBuilder.RegisterGeneric(typeof(DbADataContext<>)).Named("DbADataContext", typeof(IUnitOfWork<>));
containerBuilder.RegisterGeneric(typeof(DbBDataContext<>)).Named("DbBDataContext", typeof(IUnitOfWork<>));
If you want to keep single BaseRepository and its interface, you have to somehow configure, with entity would be handled by which DbContext. It could be done in registration part of application, but in that case you cannot register your BaseRepostory<T> as open generic, but be explicit in your registrations, like this:
containerBuilder.RegisterType<DbADataContext>().Named<IUnitOfWork>("A");
containerBuilder.RegisterType<DbBDataContext>().Named<IUnitOfWork>("B");
containerBuilder.Register(c => new BaseRepository<Team>(c.ResolveNamed<IUnitOfWork>("A")).As<IRepostory<Team>>();
containerBuilder.Register(c => new BaseRepository<Player>(c.ResolveNamed<IUnitOfWork>("B")).As<IRepository<Player>>();
(just proof of concept, code not tested)
Autofac is not smart enough to know "automatically" which unit of work you want to use in each of your repository.

Moq - Update with DbEntityEntry

I'm using EF6. The generated code is something like:
public partial class MyDataContext : DbContext
{
public MyDataContext() : base("name=mydata")
{
}
public virtual DbSet<Book> Books { get; set; }
}
Then I have a generic repository like:
public class GenericRepository<TObject> where TObject : class
{
protected readonly MyDataContext Context;
protected GenericRepository(MyDataContext context)
{
Context = context;
}
public virtual TObject Update(TObject data, int id)
{
if (data == null)
return null;
TObject obj = Context.Set<TObject>().Find(id);
if (obj != null)
{
Context.Entry(obj).CurrentValues.SetValues(data);
Context.SaveChanges();
}
return obj;
}
}
Then I have a service that uses the GenericRepository to update data:
public class MyDataService<TObject> where TObject : class
{
private readonly MyDataContext context;
public MyDataService(MyDataContext ct)
{
context = ct;
}
public TObject Update(TObject obj, int id)
{
var r = new GenericRepository<TObject>(context);
return r.Update(obj, id);
}
}
So I can update a books with something like this:
var ds = new MyDataService<Book>(new MyDataContext());
var data = ds.Update(new Book { Name = "New Name" }, 1);
This is working fine. Next I try to use Moq to unit test the above code with something like:
var updatedBook = new Book { Name = "Update Book Name" };
var mockSet = new Mock<DbSet<Book>>();
var mockContext = new Mock<MyDataContext>();
mockContext.Setup(c => c.Books).Returns(mockSet.Object);
mockContext.Setup(c => c.Set<Book>().Find(It.IsAny<object[]>()))
.Returns<object[]>(ids => chips.FirstOrDefault(d => d.Id == (int)ids[0]));
var service = new MyDataService<Book>(mockContext.Object);
var data = service.Update(updatedBook, 1);
However, I get an exception on the Context.Entry(obj).CurrentValues.SetValues(data) line.
How do I mock the Update method properly?
You could implement an interface for MyDataService to be able to mock it
public Interface IMyDataService<TObject> where TObject : class
{
TObject Update(TObject obj, int id);
}
public class MyDataService<TObject>:IMyDataService<TObject>
where TObject : class
{
private readonly MyDataContext context;
public MyDataService(MyDataContext ct)
{
context = ct;
}
public TObject Update(TObject obj, int id)
{
var r = new GenericRepository<TObject>(context);
return r.Update(obj, id);
}
}
Moq:
var mockDataService = new Mock<IMyDataService<Book>>();
mockDataService.Setup(c=> c.Update(It.Any<Book>(),It.Any<int>()).Returns(updatedbook);
The service should be dependent on the repository. Passing the context directly to the service is misleading as what the service really needs and uses is the repository.
Your classes should depend on abstractions and not on concretions. That said, all the above classes could be abstracted behind interfaces. but for now I'll focus on the service class and it's dependence on the repository. You are coupling different layers too closely. Service layer doesn't need to know about data context
Abstract the repository to allow for easier testability
interface IGenericRepository<TObject> where TObject : class {
TObject Update(TObject data, int id);
}
public class GenericRepository<TObject> : IGenericRepository<TObject> where TObject : class {
protected readonly MyDataContext Context;
public GenericRepository(MyDataContext context) {
Context = context;
}
public virtual TObject Update(TObject data, int id) {
if (data == null)
return null;
TObject obj = Context.Set<TObject>().Find(id);
if (obj != null) {
Context.Entry(obj).CurrentValues.SetValues(data);
Context.SaveChanges();
}
return obj;
}
}
The service would only now need to know about the repository abstraction, not its implementation details.
public class MyDataService<TObject> where TObject : class {
private readonly IGenericRepository<TObject> repository;
public MyDataService(IGenericRepository<TObject> repository) {
this.repository = repository;
}
public TObject Update(TObject obj, int id) {
return repository.Update(obj, id);
}
}
So now the service can be tested in isolation without any need to worry about any data context
//Arrange
var updatedBook = new Book { Name = "Update Book Name" };
var id = 1;
var mockRepository = new Mock<IGenericRepository<Book>>();
mockRepository
.Setup(m => m.Update(updatedBook, id))
.Returns(updatedBook);
var service = new MyDataService<Book>(mockRepository.Object);
//Act
var data = service.Update(updatedBook, id);
//Assert
//...
When it's time to unit test the repository implementation in isolation, then you can follow the same structure and abstract the context for the repository implementation.
I would suggest the small refactoring in order to make the testing easier and even possible. With this implementation you are relying on the implementation of the DbContext and DbEntityEntry.
At first extract interface for your context:
public inteface IMyDataContext<TObject> where TObject is class
{
TObject FindById(int id); //call FindId
void Update(TObject); //call DbEntityEntry SetValues
void SaveChanges();
}
In the GenericRepository then inject the interface. This will make your life easier, you can then easily mock all method. Unit tests of the repository should verify that right methods of the context are called.

How to mock EF 6 Async methods using JustMock?

I am trying to Mock Entity Framework 6.0.2 Async methods using JustMock. I am following testing with async queries but it is write using Moq I am trying to convert this into JustMock with help of Mock Multiple Interfaces but getting an exception :
The provider for the source IQueryable doesn't implement
IDbAsyncQueryProvider. Only providers that implement
IDbAsyncQueryProvider can be used for Entity Framework asynchronous
operations. For more details see
http://go.microsoft.com/fwlink/?LinkId=287068.
Here is my code :
var dummyData = GetEmployeeSkills();
var mockSet = Mock.Create<DbSet<EmployeeSkill>>();
(mockSet as IDbAsyncEnumerable<EmployeeSkill>).Arrange(x => x.GetAsyncEnumerator())
.Returns(new TestDbAsyncEnumerator<EmployeeSkill>(dummyData.GetEnumerator()));
(mockSet as IQueryable<EmployeeSkill>).Arrange(x => x.Provider).Returns(new TestDbAsyncQueryProvider<EmployeeSkill>(dummyData.Provider));
(mockSet as IQueryable<EmployeeSkill>).Arrange(x => x.Expression).Returns(dummyData.Expression);
(mockSet as IQueryable<EmployeeSkill>).Arrange(x => x.ElementType).Returns(dummyData.ElementType);
(mockSet as IQueryable<EmployeeSkill>).Arrange(x => x.GetEnumerator()).Returns(dummyData.GetEnumerator());
var mockContext = Mock.Create<TimeSketchContext>();
mockContext.Arrange(x => x.Set<EmployeeSkill>()).Returns(mockSet);
baseRepository = new BaseRepository<EmployeeSkill>(mockContext);
private EmployeeSkill GetEmployeeSkill()
{
return new EmployeeSkill
{
SkillDescription = "SkillDescription",
SkillName = "SkillName",
Id = 1
};
}
private IQueryable<EmployeeSkill> GetEmployeeSkills()
{
return new List<EmployeeSkill>
{
GetEmployeeSkill(),
GetEmployeeSkill(),
GetEmployeeSkill(),
}.AsQueryable();
}
Test :
[Fact]
public async Task DbTest()
{
var data = await baseRepository.FindAsync(1);
Assert.NotEqual(null, data);
}
Repository :
public class BaseRepository<T> : IRepositoryBase<T> where T : class, IEntity, new()
{
protected readonly DbContext InnerDbContext;
protected DbSet<T> InnerDbSet;
public BaseRepository(IDbContext innerDbContext)
{
InnerDbContext = innerDbContext as DbContext;
InnerDbSet = innerDbContext.Set<T>();
}
public virtual Task<T> FindAsync(long id)
{
return InnerDbSet.FirstOrDefaultAsync(x=>x.Id == id);
}
}
Interface :
public interface IDbContext
{
DbSet<T> Set<T>() where T : class;
}
Context :
public class TimeSketchContext : DbContext, IDbContext
{
public virtual DbSet<EmployeeSkill> EmployeeSkill { get; set; }
}
Because JustMock can mock non virtual methods when you are writing
var mockContext = Mock.Create<TimeSketchContext>();
mockContext.Arrange(x => x.Set<EmployeeSkill>()).Returns(mockSet);
it will mock the DbContext.Set<> and not your IDbContext.Set<> so you get the exception.
There are at least 2 solution to this:
Mock your IDbContext interface
var mockContext = Mock.Create<IDbContext>();
Or change back your BaseRepository to use a DbContext instead of your interface:
public class BaseRepository<T> : IRepositoryBase<T> where T : class, IEntity, new()
{
protected readonly DbContext InnerDbContext;
protected DbSet<T> InnerDbSet;
public BaseRepository(DbContext innerDbContext)
{
InnerDbContext = innerDbContext;
InnerDbSet = InnerDbContext.Set<T>();
}
public virtual Task<T> FindAsync(long id)
{
return InnerDbSet.FirstOrDefaultAsync(x => x.Id == id);
}
}

Decorating a generic interface with Structuremap

I have a generic interface, that takes in two generic types. I want to decorate all versions returned, but since I don't know the type when calling EnrichWith, it obviously doesn't compile. I've tried using the EnrichWith overload that passes in the context, thinking maybe I could grab the generic types passed in and call Activator.CreateInstance, but the context doesn't have any useful information on it when debugging and inspecting it.
Here's what I have so far. This is my generic interface:
public interface IServiceOperation<in TRequest, out TResponse> where TResponse : ServiceResult, new()
{
TResponse PerformService(TRequest validatedRequest);
}
Here's a sample implementation:
public class SignUpService : IServiceOperation<SignUpRequest, SignUpResult>
{
private readonly IUserRepository _userRepo;
public SignUpService(IUserRepository userRepo)
{
_userRepo = userRepo;
}
public SignUpResult PerformService(SignUpRequest validatedRequest)
{
var user = Mapper.Map<User>(validatedRequest);
user.MarkAsLoggedIn();
user.ChangePassword(validatedRequest.UnhashedPassword);
using(var transaction = _userRepo.BeginTransaction())
{
_userRepo.Save(user);
transaction.Commit();
}
return new SignUpResult();
}
}
Here is my decorator, that takes in another service as well:
public class ValidateServiceDecorator<TRequest, TResponse> : IServiceOperation<TRequest, TResponse> where TResponse : ServiceResult, new()
{
private readonly IServiceOperation<TRequest, TResponse> _serviceOperation;
private readonly IValidationService _validationService;
public ValidateServiceDecorator(IServiceOperation<TRequest, TResponse> serviceOperation,
IValidationService validationService)
{
_serviceOperation = serviceOperation;
_validationService = validationService;
}
public TResponse PerformService(TRequest request)
{
var response = new TResponse();
var validationResult = _validationService.Validate(request);
if (!validationResult.IsValid)
{
response.ValidationErrors = validationResult.ValidationErrors;
return response;
}
return _serviceOperation.PerformService(request);
}
Lastly, here is how far I've gotten on my container. This obviously doesn't compile, but the EnrichWith line shows what I'm trying to achieve:
public class StructureMapServiceScanner : Registry
{
public StructureMapServiceScanner()
{
Scan(scanner =>
{
scanner.AssemblyContainingType(typeof (IServiceOperation<,>));
scanner.ConnectImplementationsToTypesClosing(typeof (IServiceOperation<,>));
});
For(typeof (IServiceOperation<,>))
.EnrichWith((ioc, original) => new ValidateServiceDecorator(original, ioc.GetInstance<IValidationService>()));
}
}
And just because this question needed a little more code, here's my test that I'm trying to get to pass:
[TestClass]
public class StructureMapServiceScannerSpecs
{
[TestMethod]
public void Test()
{
ObjectFactory.Configure(cfg =>
{
cfg.AddRegistry<StructureMapServiceScanner>();
cfg.For<IUserRepository>().Use(new Mock<IUserRepository>().Object);
cfg.For<IValidationService>().Use(new Mock<IValidationService>().Object);
});
var service = ObjectFactory.GetInstance<IServiceOperation<SignUpRequest, SignUpResult>>();
service.ShouldNotBeNull();
service.ShouldBeType<ValidateServiceDecorator<SignUpRequest, SignUpResult>>();
}
}
I feel like this is something that should be simple, and I'm really missing something with how to use StructureMap. I could create type-specific versions for all combinations of Request and Response types, but obviously that's not desirable. So what am I missing?
Was able to figure it out, eventually. I created a RegistrationConvention:
public class ServiceRegistrationConvention : IRegistrationConvention
{
public void Process(Type type, Registry registry)
{
var interfacesImplemented = type.GetInterfaces();
foreach (var interfaceImplemented in interfacesImplemented)
{
if (interfaceImplemented.IsGenericType && interfaceImplemented.GetGenericTypeDefinition() == typeof(IServiceOperation<,>))
{
var genericParameters = interfaceImplemented.GetGenericArguments();
var closedValidatorType = typeof(ValidateServiceDecorator<,>).MakeGenericType(genericParameters);
registry.For(interfaceImplemented)
.EnrichWith((context, original) => Activator.CreateInstance(closedValidatorType, original,
context.GetInstance<IValidationService>()));
}
}
}
}
Here's an approach that still leverages StructureMap's IoC capabilities, allowing additional services to be injected easily into your decorator. It's not perfect since it assumes you are using the primary container and not a child container, but it will probably work for most scenarios.
public class ServiceRegistrationConvention : IRegistrationConvention
{
public void Process(Type type, Registry registry)
{
var handlerInterfaces = (from t in type.GetInterfaces()
where t.IsGenericType &&
t.GetGenericTypeDefinition() == typeof (IHandle<,>)
select t);
foreach (var handler in handlerInterfaces)
{
var decoratorType = typeof (ValidationDecorator<,>).MakeGenericType(handler.GetGenericArguments());
registry.For(handler)
.EnrichWith((ctx, orig) => ObjectFactory.With(handler, orig).GetInstance(decoratorType));
}
}
}
Ideally, StructureMap's IContext should expose the With method just like IContainer does. Without that, there's not really a great solution to this problem.

Categories

Resources