Closed rosdi closed 9 years ago
Nevermind.. I will just do integration test instead... and this is not even a proper place to ask this question anyway. Sorry about that.
I had the same question last week for myself. Ended up with this kind of fake. Not fully implemented obviously, but enough for now.
Note: my real services are using AmbientDbContextLocator
, but within unit tests I don't need it, that's why IDbContextCollection DbContexts
is not implemented.
/// <summary>
/// Represents fake DbContextScope for services unit testing
/// </summary>
public class FakeDbContextScope : IDbContextScope
{
private bool _disposed;
private int _saveChangesCount;
public FakeDbContextScope()
{
_disposed = false;
_saveChangesCount = 0;
}
public int SaveChanges()
{
_saveChangesCount++;
return 0;
}
public Task<int> SaveChangesAsync()
{
throw new NotImplementedException("SaveChangesAsync() is not implemented within FakeDbContextScope");
}
public Task<int> SaveChangesAsync(CancellationToken cancelToken)
{
throw new NotImplementedException("SaveChangesAsync(cancelToken) is not implemented within FakeDbContextScope");
}
public void RefreshEntitiesInParentScope(IEnumerable entities)
{
throw new NotImplementedException("RefreshEntitiesInParentScope(entities) is not implemented within FakeDbContextScope");
}
public Task RefreshEntitiesInParentScopeAsync(IEnumerable entities)
{
throw new NotImplementedException("RefreshEntitiesInParentScopeAsync(entities) is not implemented within FakeDbContextScope");
}
public IDbContextCollection DbContexts
{
get
{
throw new NotImplementedException("IDbContextCollection() is not implemented within FakeDbContextScope");
}
}
public bool Disposed
{
get
{
return _disposed;
}
}
public void Dispose()
{
_disposed = true;
}
}
Hi rosdi,
This is the right place to ask since there are no other places for these sort of questions :)
I'm assuming that you followed Microsoft's guidance to create your stub DbContext and DbSet instances.
You'd now like to get DbContextScope
to return these instead of the real instances. This is actually very easy (or I think it is anyway, I haven't actually tried it yet :) )
You'll note that the DbContextScope
constructor takes an optional IDbContextFactory
instance. The IDbContextFactory
was created to do precisely what you're trying to do: it lets you control the creation of the DbContext
instances, allowing you to e.g. substitute them with a stub implementation.
So in your unit test, simply implement the IDbContextFactory
interface and get it to return your stub DbContext
instance. Alternatively, you can of course use a mocking library like Moq to do that for you. Then, provide this "stub DbContext factory" to your DbContextScope
and you're all done.
I got it to work... thanks to both of you.. from now on I don't see any reason NOT to use DbContextScope for my future projects... cheers..
Hi. I followed the guide you referenced above for creating stub DbContext and DbSet instances but I am still having trouble mocking a service similar(really the exact same) to the 'SendWelcomeEmail' in the UserEmailService examples. I am injecting IDbContextScopeFactory (using autofac) just like the UserEmailService example(no other dependencies) but I can't figure out how to mock the service. I am using moq. Any help would be greatly appreciated. I love the work you have done, it is making all of my projects so much easier. Thanks ahead.
public async Task Test_vendor_async_service()
{
//shortened for brevity
var data = new List<Vendor>();
var mockSet = new Mock<DbSet<Vendor>>();
mockSet.As<IDbAsyncEnumerable<Vendor>>()
.Setup(m => m.GetAsyncEnumerator())
.Returns(new TestDbAsyncEnumerator<Vendor>(data.GetEnumerator()));
mockSet.As<IQueryable<Vendor>>()
.Setup(m => m.Provider)
.Returns(new TestDbAsyncQueryProvider<Vendor>(data.Provider));
mockSet.As<IQueryable<Vendor>>().Setup(m => m.Expression).Returns(data.Expression);
mockSet.As<IQueryable<Vendor>>().Setup(m => m.ElementType).Returns(data.ElementType);
mockSet.As<IQueryable<Vendor>>().Setup(m => m.GetEnumerator()).Returns(data.GetEnumerator());
//this is where I am not so sure what needs to be done.
var mockFactory = new Mock<IDbContextScopeFactory>();
mockFactory.As<MyContext>()
.Setup(c => c.Vendors).Returns(mockSet.Object);
//I can't really pass in the mockfactory because it of type Mock<T>
//confused
var service = new VendorsService(mockFactory);
var vendors = await service.GetAllAsync().ConfigureAwait(false);
Assert.AreEqual(3, vendors.Count);
Assert.AreEqual("AAA", vendors[0].BusinessName);
Assert.AreEqual("BBB", vendors[1].BusinessName);
Assert.AreEqual("ZZZ", vendors[2].BusinessName);
}
Hi, I've been working around the DbContextScope which is working fine however when it comes to Unit Testing I have to say I am a bit confused about how I should use the libraries in order to unit test a MVC application. I am trying to call the method GetScanDefinitions() and assert the objects retrieved with my predefined mockset.
This is my Unit Test class:
public class UnitScanDefinitionsControllerTest
{
private CrawlerContext _dbEntities;
private readonly ICrawlerRepository _crawlerRepository;
private IScanDefinitionService _scanDefinitionService;
public UnitScanDefinitionsControllerTest()
{
IAmbientDbContextLocator ambientDbContextLocator = new AmbientDbContextLocator();
_crawlerRepository = new CrawlerRepository(ambientDbContextLocator);
}
[Fact]
public async Task ScanDefinitionView()
{
var data = new List<ScanDefinition>
{
new ScanDefinition { Name = "AAA" },
new ScanDefinition { Name = "BBB" },
new ScanDefinition { Name = "CCC" },
}.AsQueryable();
var mockSet = new Mock<DbSet<ScanDefinition>>();
mockSet.As<IDbAsyncEnumerable<ScanDefinition>>()
.Setup(m => m.GetAsyncEnumerator())
.Returns(new DbAsyncEnumerator<ScanDefinition>(data.GetEnumerator()));
mockSet.As<IQueryable<ScanDefinition>>()
.Setup(m => m.Provider)
.Returns(new DbAsyncQueryProvider<ScanDefinition>(data.Provider));
mockSet.As<IQueryable<ScanDefinition>>().Setup(m => m.Expression).Returns(data.Expression);
mockSet.As<IQueryable<ScanDefinition>>().Setup(m => m.ElementType).Returns(data.ElementType);
mockSet.As<IQueryable<ScanDefinition>>().Setup(m => m.GetEnumerator()).Returns(data.GetEnumerator());
var mockFactory = new Mock<IDbContextFactory>();
mockFactory.Setup(c => c.CreateDbContext<CrawlerContext>()).Returns(_dbEntities);
_dbContextFactory = mockFactory.Object;
// Setup DbContextScopeFactory to use mocked context
_mockContextScope = new DbContextScopeFactory(_dbContextFactory);
_scanDefinitionService = new ScanDefinitionService(_mockContextScope, _crawlerRepository);
var scanDefinitions = await _scanDefinitionService.GetScanDefinitions().ToListAsync();
Assert.Equal(3, scanDefinitions.Count);
Assert.Equal("AAA", scanDefinitions[0].Name);
Assert.Equal("BBB", scanDefinitions[1].Name);
Assert.Equal("ZZZ", scanDefinitions[2].Name);
}
}
The Service Layer looks like this:
public class ScanDefinitionService : IScanDefinitionService
{
private readonly IDbContextScopeFactory _dbContextScopeFactory;
private readonly ICrawlerRepository _crawlerRepository;
public ScanDefinitionService(IDbContextScopeFactory dbContextScopeFactory, ICrawlerRepository context)
{
_dbContextScopeFactory = dbContextScopeFactory;
_crawlerRepository = context;
}
public IQueryable<ScanDefinition> GetScanDefinitions()
{
using (_dbContextScopeFactory.CreateReadOnly())
{
return _crawlerRepository.GetScanDefinitions();
}
}
}
And finally the Repository Layer:
public class CrawlerRepository : ICrawlerRepository
{
private readonly IAmbientDbContextLocator _ambientDbContextLocator;
private CrawlerContext DbContext
{
get
{
var dbContext = _ambientDbContextLocator.Get<CrawlerContext>();
if (dbContext == null)
throw new InvalidOperationException("Db Context Error message");
return dbContext;
}
}
public CrawlerRepository(IAmbientDbContextLocator ambientDbContextLocator)
{
if (ambientDbContextLocator == null) throw new ArgumentNullException("ambientDbContextLocator");
_ambientDbContextLocator = ambientDbContextLocator;
}
public IQueryable<ScanDefinition> GetScanDefinitions()
{
return DbContext.ScanDefinitions **<--- TEST FAILS HERE; Message: System.NullReferenceException : Object reference not set to an instance of an object.**
.Include(sd => sd.ScanBases)
.Include(sd => sd.ScanDefinitionFileTypes)
.Include(sd => sd.ScanDefinitionRuleDefinitions);
}
}
Any help would be much appreciated.
Hi, this is not an 'issue' per se but I just want to know how do I mock this so that I can add unit test? I have FakeDbSet and FakeDbContext but I could not find a way to actually inject this into DbContextScope without major changes to the source code..
Any help is appreciated... thanks.