I have a simple document manager which is injected into my controller in an asp.net c# MVC project. The project is database-first and the Document
table is indexed by documentId
, an auto incrementing integer.
I have been trying to write a test which tests the following implementation of CreateNewDocument
, which after successfully adding a document looks it up and returns the new document id.
The problem is that I can't find a way to mock MyEntityFrameWorkEntities
which I can add a document to and then search for that document using linq. I think it doesn't work because the mocked _context.Document.Add
doesn't really do anything.
My question is this: can I set up my mocks differently so I can leave the DocumentManager
as it is and write a test which passes?
public class DocumentManager : IDocumentManager
{
private readonly MyEntityFrameWorkEntities _context;
public DocumentManager(MyEntityFrameWorkEntities context)
{
_context = context;
}
public int CreateNewDocument(int userId)
{
var newDocumentGuid = Guid.NewGuid();
var newDocument = new Document
{
UserId = userId,
DateCreated = DateTime.Now,
DocumentGuid = newDocumentGuid
};
_context.Document.Add(newDocument);
_context.SaveChanges();
// the .First here doesn't return anything when called from tests
return _context.Document.First(d => d.DocumentGuid == newDocumentGuid).DocumentId;
}
}
public partial class MyEntityFrameWorkEntities : DbContext
{
public MyEntityFrameWorkEntities() : base("name=MyEntityFrameWorkEntities")
{
}
public virtual DbSet<Document> Document { get; set; }
/* ...etc... */
}
and the test class:
[TestMethod]
public void TestCreateNewDocument()
{
var mockContext = new Mock<MyEntityFrameWorkEntities>();
var mockDocumentDbSet = GetQueryableMockDocumentDbSet();
mockContext.Setup(m => m.Document).Returns(mockDocumentDbSet.Object);
var documentManager = new DocumentManager(mockContext.Object);
var newDocId = documentManager.CreateNewDocument(123);
// This line doesn't get hit as the .First falls over before here
Assert.AreNotEqual(newDocId, 0);
}
private static Mock<DbSet<Document>> GetQueryableMockDocumentDbSet()
{
var data = new List<Document> { GetDocument(111, 11), GetDocument(222, 22), GetDocument(333, 33) }.AsQueryable();
var mockDocumentDbSet = new Mock<DbSet<Document>>();
mockDocumentDbSet.As<IQueryable<Document>>().Setup(m => m.Provider).Returns(data.Provider);
mockDocumentDbSet.As<IQueryable<Document>>().Setup(m => m.Expression).Returns(data.Expression);
mockDocumentDbSet.As<IQueryable<Document>>().Setup(m => m.ElementType).Returns(data.ElementType);
mockDocumentDbSet.As<IQueryable<Document>>().Setup(m => m.GetEnumerator()).Returns(data.GetEnumerator());
return mockDocumentDbSet;
}
private static Document GetDocument(int documentId, int userId)
{
return new Document
{
DocumentId = documentId,
UserId = userId,
DateCreated = DateTime.Now.AddDays(-1),
DocumentGuid = Guid.NewGuid(),
};
}
You can set up your mock DbSet's Add() method with a callback which will add the item in to your backing List:
private static Mock<DbSet<Document>> GetQueryableMockDocumentDbSet()
{
var data = new List<Document> { GetDocument(111, 11), GetDocument(222, 22), GetDocument(333, 33) };
var mockDocumentDbSet = new Mock<DbSet<Document>>();
mockDocumentDbSet.As<IQueryable<Document>>().Setup(m => m.Provider).Returns(data.AsQueryable().Provider);
mockDocumentDbSet.As<IQueryable<Document>>().Setup(m => m.Expression).Returns(data.AsQueryable().Expression);
mockDocumentDbSet.As<IQueryable<Document>>().Setup(m => m.ElementType).Returns(data.AsQueryable().ElementType);
mockDocumentDbSet.As<IQueryable<Document>>().Setup(m => m.GetEnumerator()).Returns(data.GetEnumerator());
mockDocumentDbSet.Setup(m => m.Add(It.IsAny<Document>())).Callback<Document>(data.Add);
return mockDocumentDbSet;
}
Your subsequent call to First() should then be able to retrieve the item.
Consider mocking out at a higher abstraction layer. In this case, consider mocking out the Respository. You could go even higer and mock out the service itself.
Construct testable business layer logic
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With