0

I'm mocking a DbContext and I need the .Set(Of TEntity) method to return the contents of the underlying data store (in this case a List(Of TEntity)).

Here's my .Setup() extension method:

<Extension>
Public Sub Setup(Of TEntity As Class)(Instance As Mock(Of Db.Context), Entities As List(Of TEntity))
  Dim oReturn As Func(Of DbSet(Of TEntity))
  Dim oSetup As Expression(Of Func(Of Db.Context, DbSet(Of TEntity)))

  oReturn = Function() Entities.AsQueryable
  oSetup = Function(Context) Context.Set(Of TEntity)

  Instance.Setup(oSetup).Returns(oReturn)
End Sub

The problem is that when the oReturn function runs, an error ensues:

Unable to cast object of type IQueryable'1[Item] to type DbSet'1[Item]

As DbSet(Of TEntity) is an abstract class, it can't be directly instantiated. There doesn't seem to be a way to get that data into a DbSet. (Side note: this deepens the mystery of how EF itself manages the task—I poked through the source code a bit but came up empty.)

Neither do .AsEnumerable nor .ToArray work. Same casting error.

There's this answer, but I'm unable to make similar adjustments as this DbContext is used to manage my ASP.NET Identity store as well—the Repository Pattern doesn't apply universally in my code. I must mock the DbContext instead.

Then there's this question, but it remains unanswered.

How can I get that List(Of TEntity) data into a DbSet(Of TEntity)?

InteXX
  • 6,135
  • 6
  • 43
  • 80

1 Answers1

0

I was able to do this not by creating and populating a new DbSet(Of TEntity), as this Q&A title postulates, but rather by mocking DbSet(Of TEntity):

<Extension>
Public Sub Setup(Of TEntity As Class)(Instance As Mock(Of Db.Context), Entities As List(Of TEntity), ContextSetup As Expression(Of Func(Of Db.Context, DbSet(Of TEntity))))
  Dim oDbSetReturn As Func(Of DbSet(Of TEntity))
  Dim oDbSetSetup As Expression(Of Func(Of Db.Context, DbSet(Of TEntity)))
  Dim oDbSetMock As Mock(Of DbSet(Of TEntity))
  Dim oDbSet As DbSet(Of TEntity)

  oDbSetMock = New Mock(Of DbSet(Of TEntity))
  oDbSetMock.Setup(Entities)
  oDbSet = oDbSetMock.Object

  oDbSetReturn = Function() oDbSet
  oDbSetSetup = Function(Context) Context.Set(Of TEntity)

  Instance.Setup(ContextSetup).Returns(oDbSet)
  Instance.Setup(oDbSetSetup).Returns(oDbSetReturn)
End Sub

This is a companion method to a DbSet(Of TEntity) setup:

<Extension>
Public Sub Setup(Of TEntity As Class)(Instance As Mock(Of DbSet(Of TEntity)), Entities As List(Of TEntity))
  Dim oRemoveRangeReturn As Func(Of IEnumerable(Of TEntity), IEnumerable(Of TEntity))
  Dim oRemoveRangeSetup As Expression(Of Func(Of DbSet(Of TEntity), IEnumerable(Of TEntity)))
  Dim oAddRangeReturn As Func(Of IEnumerable(Of TEntity), IEnumerable(Of TEntity))
  Dim oAddRangeSetup As Expression(Of Func(Of DbSet(Of TEntity), IEnumerable(Of TEntity)))
  Dim oRemoveReturn As Func(Of TEntity, TEntity)
  Dim oRemoveSetup As Expression(Of Func(Of DbSet(Of TEntity), TEntity))
  Dim oAddReturn As Func(Of TEntity, TEntity)
  Dim oAddSetup As Expression(Of Func(Of DbSet(Of TEntity), TEntity))

  Instance.As(Of IQueryable(Of TEntity)).Setup(Function(Queryable) Queryable.GetEnumerator).Returns(Entities.AsQueryable.GetEnumerator)
  Instance.As(Of IQueryable(Of TEntity)).Setup(Function(Queryable) Queryable.ElementType).Returns(Entities.AsQueryable.ElementType)
  Instance.As(Of IQueryable(Of TEntity)).Setup(Function(Queryable) Queryable.Expression).Returns(Entities.AsQueryable.Expression)
  Instance.As(Of IQueryable(Of TEntity)).Setup(Function(Queryable) Queryable.Provider).Returns(Entities.AsQueryable.Provider)

  oRemoveRangeSetup = Function(DbSet) DbSet.RemoveRange(It.IsAny(Of IEnumerable(Of TEntity)))
  oAddRangeSetup = Function(DbSet) DbSet.AddRange(It.IsAny(Of IEnumerable(Of TEntity)))
  oRemoveSetup = Function(DbSet) DbSet.Remove(It.IsAny(Of TEntity))
  oAddSetup = Function(DbSet) DbSet.Add(It.IsAny(Of TEntity))

  oRemoveRangeReturn = Function(Range)
                         Entities = Entities.Except(Range)
                         Return Entities
                       End Function

  oAddRangeReturn = Function(Range)
                      Entities.AddRange(Range)
                      Return Entities
                    End Function

  oRemoveReturn = Function(Entity)
                    Entities.Remove(Entity)
                    Return Entity
                  End Function

  oAddReturn = Function(Entity)
                 Entities.Add(Entity)
                 Return Entity
               End Function

  Instance.Setup(oRemoveRangeSetup).Returns(oRemoveRangeReturn)
  Instance.Setup(oAddRangeSetup).Returns(oAddRangeReturn)
  Instance.Setup(oRemoveSetup).Returns(oRemoveReturn)
  Instance.Setup(oAddSetup).Returns(oAddReturn)
End Sub

It's called like this:

Private ReadOnly Property DbContextFactory As Func(Of Db.Context)
  Get
    Dim oContextMock As Mock(Of Db.Context)

    Return Function()
             oContextMock = New Mock(Of Db.Context)
             oContextMock.Setup(Of Db.City)(Me.Cities, Function(Context) Context.Cities)
             oContextMock.Setup(Of Db.Role)(Me.Roles, Function(Context) Context.Roles)
             oContextMock.Setup(Of Db.User)(Me.Users, Function(Context) Context.Users)

             Return oContextMock.Object
           End Function
  End Get
End Property

This should work. I'm unable to test it presently, but I'll do so asap and provide the results.

--EDIT 1--

I'm getting an 'Unsupported Expression' error at Instance.Setup(ContextSetup). I've opened a ticket at the Moq repo for this.

--EDIT 2--

Once again, stakx to the rescue.

The solution is found in the casting. Most models want a DbSet(Of T), while the two Identity-derived models (Db.Role and Db.User) want an IDbSet(Of T).

This results in a pair of overloaded extension methods:

<Extension>
Public Sub Setup(Of TEntity As Class)(
    Instance As Mock(Of Db.Context),
    Entities As List(Of TEntity),
    DbSetSetup As Expression(Of Func(Of Db.Context, IDbSet(Of TEntity)))
  )

  Instance.Setup(DbSetSetup).Returns(DbSet(Entities))
  Instance.Setup(MethodSetup(Of TEntity)).Returns(MethodReturn(Entities))
End Sub

<Extension>
Public Sub Setup(Of TEntity As Class)(
    Instance As Mock(Of Db.Context),
    Entities As List(Of TEntity),
    DbSetSetup As Expression(Of Func(Of Db.Context, DbSet(Of TEntity)))
  )

  Instance.Setup(DbSetSetup).Returns(DbSet(Entities))
  Instance.Setup(MethodSetup(Of TEntity)).Returns(MethodReturn(Entities))
End Sub

Private Function MethodSetup(Of TEntity As Class)() As Expression(Of Func(Of Db.Context, DbSet(Of TEntity)))
  Return Function(Context) Context.Set(Of TEntity)
End Function

Private Function MethodReturn(Of TEntity As Class)(Entities As List(Of TEntity)) As Func(Of DbSet(Of TEntity))
  Return Function() DbSet(Entities)
End Function

Private Function DbSet(Of TEntity As Class)(Entities As List(Of TEntity)) As DbSet(Of TEntity)
  With New Mock(Of DbSet(Of TEntity))
    .Setup(Entities)
    Return .Object
  End With
End Function

And finally this all works.

Thanks stakx.

InteXX
  • 6,135
  • 6
  • 43
  • 80