c# - test - Pruebas unitarias con EF4 "Code First" y Repository
unit test rest api c# (3)
Estoy tratando de manejar las pruebas de Unidad de una aplicación de prueba ASP.NET MVC muy sencilla que he creado utilizando el enfoque Code First en el último EFTP CTP. No tengo mucha experiencia con pruebas / burlas unitarias, etc.
Esta es mi clase de repositorio:
public class WeightTrackerRepository
{
public WeightTrackerRepository()
{
_context = new WeightTrackerContext();
}
public WeightTrackerRepository(IWeightTrackerContext context)
{
_context = context;
}
IWeightTrackerContext _context;
public List<WeightEntry> GetAllWeightEntries()
{
return _context.WeightEntries.ToList();
}
public WeightEntry AddWeightEntry(WeightEntry entry)
{
_context.WeightEntries.Add(entry);
_context.SaveChanges();
return entry;
}
}
Este es IWeightTrackerContext
public interface IWeightTrackerContext
{
DbSet<WeightEntry> WeightEntries { get; set; }
int SaveChanges();
}
... y su implementación, WeightTrackerContext
public class WeightTrackerContext : DbContext, IWeightTrackerContext
{
public DbSet<WeightEntry> WeightEntries { get; set; }
}
En mi prueba, tengo lo siguiente:
[TestMethod]
public void Get_All_Weight_Entries_Returns_All_Weight_Entries()
{
// Arrange
WeightTrackerRepository repos = new WeightTrackerRepository(new MockWeightTrackerContext());
// Act
List<WeightEntry> entries = repos.GetAllWeightEntries();
// Assert
Assert.AreEqual(5, entries.Count);
}
Y mi MockWeightTrackerContext:
class MockWeightTrackerContext : IWeightTrackerContext
{
public MockWeightTrackerContext()
{
WeightEntries = new DbSet<WeightEntry>();
WeightEntries.Add(new WeightEntry() { Date = DateTime.Parse("01/06/2010"), Id = 1, WeightInGrams = 11200 });
WeightEntries.Add(new WeightEntry() { Date = DateTime.Parse("08/06/2010"), Id = 2, WeightInGrams = 11150 });
WeightEntries.Add(new WeightEntry() { Date = DateTime.Parse("15/06/2010"), Id = 3, WeightInGrams = 11120 });
WeightEntries.Add(new WeightEntry() { Date = DateTime.Parse("22/06/2010"), Id = 4, WeightInGrams = 11100 });
WeightEntries.Add(new WeightEntry() { Date = DateTime.Parse("29/06/2010"), Id = 5, WeightInGrams = 11080 });
}
public DbSet<WeightEntry> WeightEntries { get;set; }
public int SaveChanges()
{
throw new NotImplementedException();
}
}
Mi problema ocurre cuando intento crear algunos datos de prueba ya que no puedo crear un DbSet<>
ya que no tiene constructor. Me da la sensación de que estoy ladrando al árbol equivocado con todo mi enfoque tratando de burlarme de mi contexto. Cualquier consejo sería bienvenido a este novato de pruebas unitarias completas.
Probablemente obtengas un error
AutoFixture was unable to create an instance from System.Data.Entity.DbSet`1[...], most likely because it has no public constructor, is an abstract or non-public type.
El DbSet tiene un constructor protegido.
El enfoque generalmente utilizado para respaldar la capacidad de prueba crea su propia implementación del conjunto de Db
public class MyDbSet<T> : IDbSet<T> where T : class
{
Use esto como
public class MyContext : DbContext
{
public virtual MyDbSet<Price> Prices { get; set; }
}
Si mira la siguiente pregunta SO, responda 2ns, debería poder encontrar la implementación completa de un DbSet personalizado.
Pruebas unitarias con EF4 "Code First" y Repository
Entonces el
var priceService = fixture.Create<PriceService>();
No debería lanzar una excepción.
Sobre la base de lo que dijo Daz, (y como mencioné en sus comentarios), vas a necesitar hacer una implementación ''falsa'' de IDbSet. Puede encontrar un ejemplo para CTP 4 here pero para que funcione, deberá personalizar su método Find y agregar valores devueltos para algunos de los métodos anteriormente anulados, como Agregar.
El siguiente es mi propio ejemplo elaborado para CTP 5:
public class InMemoryDbSet<T> : IDbSet<T> where T : class
{
readonly HashSet<T> _data;
readonly IQueryable _query;
public InMemoryDbSet()
{
_data = new HashSet<T>();
_query = _data.AsQueryable();
}
public T Add(T entity)
{
_data.Add(entity);
return entity;
}
public T Attach(T entity)
{
_data.Add(entity);
return entity;
}
public TDerivedEntity Create<TDerivedEntity>() where TDerivedEntity : class, T
{
throw new NotImplementedException();
}
public T Create()
{
return Activator.CreateInstance<T>();
}
public virtual T Find(params object[] keyValues)
{
throw new NotImplementedException("Derive from FakeDbSet and override Find");
}
public System.Collections.ObjectModel.ObservableCollection<T> Local
{
get { return new System.Collections.ObjectModel.ObservableCollection<T>(_data); }
}
public T Remove(T entity)
{
_data.Remove(entity);
return entity;
}
public IEnumerator<T> GetEnumerator()
{
return _data.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return _data.GetEnumerator();
}
public Type ElementType
{
get { return _query.ElementType; }
}
public Expression Expression
{
get { return _query.Expression; }
}
public IQueryProvider Provider
{
get { return _query.Provider; }
}
}
Usted crea un DbSet a través del método de Fábrica Set () en el Contexto, pero no desea ninguna dependencia de EF en su prueba unitaria. Por lo tanto, lo que debe observar es implementar un stub de DbSet utilizando la interfaz IDbSet o un Stub usando uno de los frameworks Mocking como Moq o RhinoMock. Suponiendo que haya escrito su propio Stub, simplemente agregará los objetos WeightEntry a un hashset interno.
Puede tener más suerte aprendiendo sobre EF de pruebas unitarias si busca ObjectSet y IObjectSet. Estas son las contrapartidas de DbSet antes del primer lanzamiento del CTP y tienen mucho más escrito sobre ellas desde una perspectiva de prueba unitaria.
Aquí hay un excelente artículo sobre MSDN que analiza la capacidad de prueba del código EF. Utiliza IObjectSet, pero creo que sigue siendo relevante.
Como respuesta al comentario de David, estoy agregando este apéndice a continuación ya que no encajaría en los comentarios. ¿No estoy seguro si esta es la mejor práctica para respuestas de comentarios largos?
Debe cambiar la interfaz IWeightTrackerContext para devolver un IDbSet de la propiedad WeightEntries en lugar de un tipo concreto de DbSet. A continuación, puede crear un MockContext con un marco de burla (recomendado) o con su propio talón personalizado. Esto devolvería un StubDbSet de la propiedad WeightEntries.
Ahora también tendrá un código (es decir, repositorios personalizados) que dependen del IWeightTrackerContext que en producción pasaría en su Entity Framework WeightTrackerContext que implementaría IWeightTrackerContext. Esto tiende a hacerse a través de la inyección de constructor utilizando un marco IoC como Unity. Para probar el código del repositorio que depende de EF, pasaría la implementación de MockContext para que el código bajo prueba piense que está hablando con la base de datos y la base de datos "real" y se comporta (con suerte) como se esperaba. Como ha eliminado la dependencia del sistema DB externo modificable y EF, puede verificar de manera fiable las llamadas al repositorio en las pruebas de su unidad.
Una gran parte de los marcos de burla está brindando la capacidad de verificar llamadas en objetos simulados para probar el comportamiento. En su ejemplo anterior, su prueba solo está probando la funcionalidad Agregar DbSet, que no debería ser su problema, ya que MS tendrá pruebas unitarias para eso. Lo que querría saber es que la llamada a Add on DbSet se realizó desde su propio código de repositorio, si corresponde, y es ahí donde entran los frameworks de Mock.
Lo siento, sé que esto es mucho para digerir, pero si lees ese artículo, mucho se aclarará, ya que Scott Allen es mucho mejor a la hora de explicar esto que yo.