views:

894

answers:

3

Dear gurus,

I am taking my first steps with MsTest and Moq and would like to unit test a Linq2SQL repository class. The problem is that I do not want the unit tests to permantly modify my development database.

Which would be the best approach for this scenario?

  • Let each test operate on my real development database, but make sure each test cleans up after itself
  • Create a duplicate of my development database and dbml for the unit test and use that context instead so I can clear the entire database before each test run
  • Find some elaborate way of mocking the Datacontext (please bear in mind that I am a total Moq noob).
  • Something completely different? Perhaps something that would automate setting up the database for me before each test run?

Edit: I just learned that MBUnit has a rollback attribute that reverses any database operations run by a test case. I am not particularly attached to MSTest, so could this be an easy answer to my problem?

+8  A: 

I went with mocking/faking the database using some wrapper classes + a fake implementation based on http://andrewtokeley.net/archive/2008/07/06/mocking-linq-to-sql-datacontext.aspx. Note that I did end up implementing SubmitChanges logic in my fake data context wrapper to test out the validation logic in my entity's partial class implementation. I think that this was really the only tricky part which differed substantially from Tokeley's implementation.

I'll include my FakeDataContextWrapper implementation below:

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
}
tvanfosson
Yup, its what I did too. That, and created integration tests that ran against a database. Linq to Sql is pretty good in that respect--you can nuke and create the database right from the data context.
Will
+1  A: 

I played a bit with MBUnit and learned that, for most test cases, you can get away without mocking the datacontext by using MBUnit's [ROLLBACK] attribute.

Unfortunately there are also cases when the attribute produces strange side effects, such as loading a linq entity from the database, changing one property (without submitchanges), then loading the same entity again. Usually this results in no update query on the database, but from within the Test Method it appears as if the update is immediately executed as soon as I change the linq entity property.

Not a perfect solution, but I think I'll go with the [ROLLBACK] attribute since it's less effort and works well enough for me.

Adrian Grigore
+1  A: 

I had a similar need - to unit test the Linq to Sql classes, so I made a small set of classes to get mock datacontext, ITables and IQueryables into the queries.

I put the code in a blog post "Mock and Stub for Linq to Sql". It uses Moq, and might provide enough functionality for the tests you're after without hitting the database.

marklam