unit-testing - unit - testing with mocha and chai
Unidad probando un repositorio LINQ2SQL (3)
Estoy dando mis primeros pasos con MsTest y Moq y me gustaría probar de forma unitaria una clase de repositorio Linq2SQL. El problema es que no quiero que las pruebas de la unidad modifiquen permanentemente mi base de datos de desarrollo.
¿Cuál sería el mejor enfoque para este escenario?
- Deje que cada prueba opere en mi base de datos de desarrollo real, pero asegúrese de que cada prueba se limpie después de sí misma
- Cree un duplicado de mi base de datos de desarrollo y dbml para la prueba unitaria y use ese contexto en su lugar para poder borrar toda la base de datos antes de cada prueba
- Encuentre alguna forma elaborada de burlarse del Datacontext (tenga en cuenta que soy un novato de Moq total).
- Algo completamente diferente? ¿Tal vez algo que automatizaría la configuración de la base de datos antes de cada prueba?
Editar: Acabo de enterarme de que MBUnit tiene un atributo de reversión que invierte las operaciones de la base de datos ejecutadas por un caso de prueba. No estoy particularmente apegado a MSTest, ¿podría ser una respuesta fácil a mi problema?
Fui con burlas / falsificación de la base de datos usando algunas clases contenedoras + una implementación falsa basada en http://andrewtokeley.net/archive/2008/07/06/mocking-linq-to-sql-datacontext.aspx . Tenga en cuenta que terminé implementando la lógica SubmitChanges en mi contenedor de contexto de datos falso para probar la lógica de validación en la implementación de la clase parcial de mi entidad. Creo que esta fue realmente la única parte difícil que difirió sustancialmente de la implementación de Tokeley.
Voy a incluir mi implementación de FakeDataContextWrapper a continuación:
public class FakeDataContextWrapper : IDataContextWrapper
{
public DataContext Context
{
get { return null; }
}
private List<object> Added = new List<object>();
private List<object> Deleted = new List<object>();
private readonly IFakeDatabase mockDatabase;
public FakeDataContextWrapper( IFakeDatabase database )
{
mockDatabase = database;
}
protected List<T> InternalTable<T>() where T : class
{
return (List<T>)mockDatabase.Tables[typeof( T )];
}
#region IDataContextWrapper Members
public virtual IQueryable<T> Table<T>() where T : class
{
return mockDatabase.GetTable<T>();
}
public virtual ITable Table( Type type )
{
return new FakeTable( mockDatabase.Tables[type], type );
}
public virtual void DeleteAllOnSubmit<T>( IEnumerable<T> entities ) where T : class
{
foreach (var entity in entities)
{
DeleteOnSubmit( entity );
}
}
public virtual void DeleteOnSubmit<T>( T entity ) where T : class
{
this.Deleted.Add( entity );
}
public virtual void InsertAllOnSubmit<T>( IEnumerable<T> entities ) where T : class
{
foreach (var entity in entities)
{
InsertOnSubmit( entity );
}
}
public virtual void InsertOnSubmit<T>( T entity ) where T : class
{
this.Added.Add( entity );
}
public virtual void SubmitChanges()
{
this.SubmitChanges( ConflictMode.FailOnFirstConflict );
}
public virtual void SubmitChanges( ConflictMode failureMode )
{
try
{
foreach (object obj in this.Added)
{
MethodInfo validator = obj.GetType().GetMethod( "OnValidate", BindingFlags.Instance | BindingFlags.NonPublic );
if (validator != null)
{
validator.Invoke( obj, new object[] { ChangeAction.Insert } );
}
this.mockDatabase.Tables[obj.GetType()].Add( obj );
}
this.Added.Clear();
foreach (object obj in this.Deleted)
{
MethodInfo validator = obj.GetType().GetMethod( "OnValidate", BindingFlags.Instance | BindingFlags.NonPublic );
if (validator != null)
{
validator.Invoke( obj, new object[] { ChangeAction.Delete } );
}
this.mockDatabase.Tables[obj.GetType()].Remove( obj );
}
this.Deleted.Clear();
foreach (KeyValuePair<Type, IList> tablePair in this.mockDatabase.Tables)
{
MethodInfo validator = tablePair.Key.GetMethod( "OnValidate", BindingFlags.Instance | BindingFlags.NonPublic );
if (validator != null)
{
foreach (object obj in tablePair.Value)
{
validator.Invoke( obj, new object[] { ChangeAction.Update } );
}
}
}
}
catch (TargetInvocationException e)
{
throw e.InnerException;
}
}
public void Dispose() { }
#endregion
}
Jugué un poco con MBUnit y aprendí que, para la mayoría de los casos de prueba, puede escaparse sin burlarse del contexto de datos utilizando el atributo [ROLLBACK] de MBUnit.
Lamentablemente, también hay casos en los que el atributo produce efectos secundarios extraños, como cargar una entidad linq de la base de datos, cambiar una propiedad (sin enviar cambios) y luego cargar la misma entidad nuevamente. Por lo general, esto no genera ninguna consulta de actualización en la base de datos, pero desde el Método de prueba, parece que la actualización se ejecuta inmediatamente tan pronto como cambio la propiedad de la entidad linq.
No es una solución perfecta, pero creo que iré con el atributo [ROLLBACK] ya que es un esfuerzo menor y funciona lo suficientemente bien para mí.
Tenía una necesidad similar: probar las clases de Linq en Sql, así que hice un pequeño conjunto de clases para obtener el contexto de datos simulado, ITables e IQueryables en las consultas.
Puse el código en una publicación de blog " Simulacro y trozo para Linq a Sql ". Utiliza Moq, y puede proporcionar suficiente funcionalidad para las pruebas que estás buscando sin golpear la base de datos.