Vado all'unità testare un servizio che utilizza Entity Framework 6.
Scenario di esempio, avrebbe blog e post entità, blog con zero o più post. Nel metodo di servizio restituirebbe l'elenco delle entità che hanno il titolo del blog e il titolo del primo post di quel blog. Sembra di seguito
public class BlogService
{
private IBloggingContext _context;
public BlogService(IBloggingContext context)
{
_context = context;
}
public List<BlogPostSumarry> GeBlogSummary()
{
var query = from b in _context.Blogs
orderby b.Name
select new BlogPostSumarry
{
BlogTitle = b.Name,
PostTitle = b.Posts.FirstOrDefault().Title
};
return query.ToList();
}
}
}
Metodo di test unitario
[Test]
public void GeBlogSummary_WhenMatchFound()
{
var post = new List<Post>()
{
new Post() {PostId=45, Title="abc"}
};
var data = new List<Blog>
{
new Blog { Name = "BBB" },
new Blog { Name = "ZZZ" },
new Blog { Name = "AAA" },
}.AsQueryable();
var mockSet = new Mock<DbSet<Blog>>();
mockSet.As<IQueryable<Blog>>().Setup(m => m.Provider).Returns(data.Provider);
mockSet.As<IQueryable<Blog>>().Setup(m => m.Expression).Returns(data.Expression);
mockSet.As<IQueryable<Blog>>().Setup(m => m.ElementType).Returns(data.ElementType);
mockSet.As<IQueryable<Blog>>().Setup(m => m.GetEnumerator()).Returns(data.GetEnumerator());
var mockContext = new Mock<BloggingContext>();
mockContext.Setup(c => c.Blogs).Returns(mockSet.Object);
var service = new BlogService(mockContext.Object);
var blogs = service.GeBlogSummary();
Assert.AreEqual(3, blogs.Count);
Assert.AreEqual("AAA", blogs[0].BlogTitle);
Assert.AreEqual("BBB", blogs[1].BlogTitle);
Assert.AreEqual("ZZZ", blogs[2].BlogTitle);
}
Test unitario che utilizzo per simulare l'uso di DbContext e DbSet nei dati di memoria. Il problema è che, nel caso in cui i blog non abbiano alcun test post-unit, fornirà un'eccezione di riferimento nulla dove, come nel caso reale (Database), funziona bene. Questo perché in un caso in memoria è Linq a Object dove, come in EF, è Linq to Entity.
Per un esempio se cambio il metodo come sotto funziona Unit Test
public List<BlogPostSumarry> GeBlogSummary()
{
var query = from b in _context.Blogs
orderby b.Name
select new BlogPostSumarry
{
BlogTitle = b.Name,
//Manually checks for null validation works but any approach without change code for sack of unit test
PostTitle = b.Posts.Any() ? b.Posts.FirstOrDefault().Title : null
};
return query.ToList();
}
Una volta che l'approccio è cambiato, linq query per il controllo nullo, ma penso che non sia un buon approccio. In caso affermativo, come posso ottenere il mio test?
Nella mia esperienza non si eseguono test unitari isolati per il livello del database. Fai test di integrazione per questo codice. Si imposta un vero database (può essere LocalDB, è facile da configurare) ed eseguire i test su questo database di test.
Quando provi a prendere in giro il tuo database, stai testando una simulazione del tuo livello di database, non la cosa reale. E potresti passare molto tempo ad armeggiare con il mock, ma non si comporterà mai come il database si comporterà (a meno che tu non abbia intenzione di scrivere una copia del tuo database).
Una delle ragioni della differenziazione: il LINQ per gli oggetti di memoria funziona in modo diverso rispetto a quando è tradotto in SQL. Puoi facilmente scrivere LINQ che sarà perfettamente testabile con i mock e avrai dei test passabili. Ma se eseguito su un DB reale, lo stesso LINQ fallirà perché il provider LINQ non saprebbe come tradurlo in SQL.
Qualunque cosa tu faccia con la tua simulazione, il tuo oggetto di memoria non sarà lo stesso delle richieste SQL attivate sul tuo DB. E le richieste SQL sono ciò che stai veramente testando. Se non li stai testando, non ha senso fingere che ci sia un DB dietro lo storage. Potrebbe anche saltare questi test.