Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a way to generically mock the DbSet.Find method with Moq?

I'm currently using an extension method to generically mock DbSets as a list:

    public static DbSet<T> AsDbSet<T>(this List<T> sourceList) where T : class
    {
        var queryable = sourceList.AsQueryable();
        var mockDbSet = new Mock<DbSet<T>>();
        mockDbSet.As<IQueryable<T>>().Setup(m => m.Provider).Returns(queryable.Provider);
        mockDbSet.As<IQueryable<T>>().Setup(m => m.Expression).Returns(queryable.Expression);
        mockDbSet.As<IQueryable<T>>().Setup(m => m.ElementType).Returns(queryable.ElementType);
        mockDbSet.As<IQueryable<T>>().Setup(m => m.GetEnumerator()).Returns(queryable.GetEnumerator());
        mockDbSet.Setup(x => x.Add(It.IsAny<T>())).Callback<T>(sourceList.Add);
        mockDbSet.Setup(x => x.Remove(It.IsAny<T>())).Returns<T>(x => { if (sourceList.Remove(x)) return x; else return null; } );

        return mockDbSet.Object;
    }

However, I can't figure out a way to mock the Find method, which searches based on the table's primary key. I could do it at a specific level for each table because I can inspect the database, get the PK, and then just mock the Find method for that field. But then I can't use the generic method.

I suppose I could also go add to the partial classes that EF auto-generated to mark which field is the PK with an attribute or something. But we have over 100 tables and it makes the code more difficult to manage if you're relying on people to manually maintain this.

Does EF6 provide any way of finding the primary key, or does it only know dynamically after it's connected to the database?

like image 959
C. Williamson Avatar asked Oct 04 '16 16:10

C. Williamson


2 Answers

As far as I can tell, there is no 'best practice' answer to this question, but here's how I've approached it. I've added an optional parameter to the AsDbSet method which identifies the primary key, then the Find method can be mocked up easily.

public static DbSet<T> AsDbSet<T>(this List<T> sourceList, Func<T, object> primaryKey = null) where T : class
{
    //all your other stuff still goes here

    if (primaryKey != null)
    {
        mockSet.Setup(set => set.Find(It.IsAny<object[]>())).Returns((object[] input) => sourceList.SingleOrDefault(x => (Guid)primaryKey(x) == (Guid)input.First()));
    }

    ...
}

I've written this on the assumption of a single guid being used as primary key as that seemed to be how you're working, but the principle should be easy enough to adapt if you need more flexibility for composite keys, etc.

like image 191
Tim Avatar answered Oct 20 '22 08:10

Tim


After pondering this for awhile, I think I've found the "best" solution currently available. I just have a series of if statements that directly checks the type in the extension method. Then I cast to the type I need to set the find behavior and cast it back to generic when I'm done. It's only pseudo-generic, but I can't think of anything else better.

        if (typeof(T) == typeof(MyFirstSet))
        {
            mockDbSet.Setup(x => x.Find(It.IsAny<object[]>())).Returns<object[]>(x => (sourceList as List<MyFirstSet>).FirstOrDefault(y => y.MyFirstSetKey == (Guid)x[0]) as T);
        }
        else if (typeof(T) == typeof(MySecondSet))
        {
            mockDbSet.Setup(x => x.Find(It.IsAny<object[]>())).Returns<object[]>(x => (sourceList as List<MySecondSet>).FirstOrDefault(y => y.MySecondSetKey == (Guid)x[0]) as T);
        }
        ...       
like image 40
C. Williamson Avatar answered Oct 20 '22 10:10

C. Williamson