Asp.Net Core Testing Repositories and Mocking EF

I wasted the better part of the day implementing simple tests.   Even the most mundane, basic steps that have typically taken no time, were prolonged.  I remember an automated tool inside Visual Studio in 2010 or so, when you could right click on a project or class (context menu) in solution explorer, and select “Create Unit Tests”, and not only would it create a test project with the associated project references and accessors for private objects/methods, but would also offer a dialog to select which methods to create unit test coverage for.   That is not my current experience, which is mostly DIY.

First creating a new test project, did not seem to appear correctly in the solution explorer, under solution items; I had to recreate it several times.  Then I could not get my asp.net core to load inside the test project (which I suspect was being saved as a standard project, despite choosing core).  Then I could not get Moq to load.  I could not find the correct EntityFramework core reference for the test project.  I tried both xunit and mstest projects.  I tried CLI and from inside VS.

Finally, I it is working, and I am not certain if I could repeat my successful steps without some repeat mis-steps again.  In solution explorer, right-click the project => add new project => dotnet core => xunit project.

My resulting .csproj file looks like this:

 

<Project Sdk="Microsoft.NET.Sdk">

 <PropertyGroup> 
 <TargetFramework>netcoreapp1.1</TargetFramework>
 </PropertyGroup>

 <ItemGroup>
 <PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="1.1.2" />
 <PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.0.0" />
 <PackageReference Include="Moq" Version="4.7.10" />
 <PackageReference Include="xunit" Version="2.2.0" />
 <PackageReference Include="xunit.runner.visualstudio" Version="2.2.0" />
 </ItemGroup>

 <ItemGroup>
 <ProjectReference Include="..\backend\backend.csproj" />
 </ItemGroup>

</Project>

 

The next step was to implement repository testing.  This was an even better exercise in frustration; I nearly gave up a half dozen times.   Ultimately, this link on Microsoft Documentation site is the overall approach:  Entity Framework Testing with a Mocking Framework (EF6 onwards), but this will not work fully as written for asp.net core (esp. for async).  I was able to modify the MS documentation with this Stack Overflow answer.  Finally, I found Armen Shimoon’s Mocking DbSet Helpers which really cleaned up the code.

I put these pieces together as follows, starting with Armen’s piece, but adding async functionality :

 /// <summary>
 /// From http://dotnetliberty.com/index.php/2016/02/22/moq-on-net-core/
 /// </summary>
 /// 
 public static class DbSetMock
 {
 public static Mock<DbSet<T>> Create<T>(params T[] elements) where T : class
 {
 return new List<T>(elements).AsDbSetMock();
 }
 }

 public static class ListExtensions
 {
 public static Mock<DbSet<T>> AsDbSetMock<T>(this List<T> list) where T : class
 {
 IQueryable<T> queryableList = list.AsQueryable();
 Mock<DbSet<T>> dbSetMock = new Mock<DbSet<T>>();
 dbSetMock.As<IQueryable<T>>().Setup(x => x.Provider).Returns(queryableList.Provider);
 dbSetMock.As<IQueryable<T>>().Setup(x => x.Expression).Returns(queryableList.Expression);
 dbSetMock.As<IQueryable<T>>().Setup(x => x.ElementType).Returns(queryableList.ElementType);
 dbSetMock.As<IQueryable<T>>().Setup(x => x.GetEnumerator()).Returns(queryableList.GetEnumerator());


 dbSetMock.As<IAsyncEnumerable<T>>()
 .Setup(m => m.GetEnumerator())
 .Returns(new TestAsyncEnumerator<T>(queryableList.GetEnumerator()));

 dbSetMock.As<IQueryable<T>>()
 .Setup(m => m.Provider)
 .Returns(new TestAsyncQueryProvider<T>(queryableList.Provider));


 return dbSetMock;
 }


 }

Then here is the async functionality code:

 /// <summary>
 /// From https://stackoverflow.com/questions/40476233/how-to-mock-an-async-repository-with-entity-framework-core#answer-40491640
 /// </summary>
 /// <typeparam name="TEntity"></typeparam>
 internal class TestAsyncQueryProvider<TEntity> : IAsyncQueryProvider
 {
 private readonly IQueryProvider _inner;

 internal TestAsyncQueryProvider(IQueryProvider inner)
 {
 _inner = inner;
 }

 public IQueryable CreateQuery(Expression expression)
 {
 return new TestAsyncEnumerable<TEntity>(expression);
 }

 public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
 {
 return new TestAsyncEnumerable<TElement>(expression);
 }

 public object Execute(Expression expression)
 {
 return _inner.Execute(expression);
 }

 public TResult Execute<TResult>(Expression expression)
 {
 return _inner.Execute<TResult>(expression);
 }

 public IAsyncEnumerable<TResult> ExecuteAsync<TResult>(Expression expression)
 {
 return new TestAsyncEnumerable<TResult>(expression);
 }

 public Task<TResult> ExecuteAsync<TResult>(Expression expression, CancellationToken cancellationToken)
 {
 return Task.FromResult(Execute<TResult>(expression));
 }
 }

 internal class TestAsyncEnumerable<T> : EnumerableQuery<T>, IAsyncEnumerable<T>, IQueryable<T>
 {
 public TestAsyncEnumerable(IEnumerable<T> enumerable)
 : base(enumerable)
 { }

 public TestAsyncEnumerable(Expression expression)
 : base(expression)
 { }

 public IAsyncEnumerator<T> GetEnumerator()
 {
 return new TestAsyncEnumerator<T>(this.AsEnumerable().GetEnumerator());
 }

 IQueryProvider IQueryable.Provider
 {
 get { return new TestAsyncQueryProvider<T>(this); }
 }
 }

 internal class TestAsyncEnumerator<T> : IAsyncEnumerator<T>
 {
 private readonly IEnumerator<T> _inner;

 public TestAsyncEnumerator(IEnumerator<T> inner)
 {
 _inner = inner;
 }

 public void Dispose()
 {
 _inner.Dispose();
 }

 public T Current
 {
 get
 {
 return _inner.Current;
 }
 }

 public Task<bool> MoveNext(CancellationToken cancellationToken)
 {
 return Task.FromResult(_inner.MoveNext());
 }
 }

 

Finally, here were the repository tests:

 static DbContextOptions<ApiFpn2DbContext> _options = new DbContextOptionsBuilder<ApiFpn2DbContext>()
.UseInMemoryDatabase(databaseName: "ApiFpn2DbContext_testDatabase")
.Options;

 [Fact]
 public async void AddAsyncTest()
 {
 var mockSet = new Mock<DbSet<UserProfile>>();

 var mockContext = new Mock<ApiFpn2DbContext>(_options);
 mockContext.Setup(m => m.UserProfiles).Returns(mockSet.Object);

 var service = new UserProfileRepository(mockContext.Object);
 var result = await service.AddAsync(new UserProfile {
 Id="1",
 Username="user1"
 });
 mockContext.Object.SaveChanges();

 mockSet.Verify(m => m.AddAsync(It.IsAny<UserProfile>(), new CancellationToken()), Times.Once());
 mockContext.Verify(m => m.SaveChanges(), Times.Once());
 }

 [Fact]
 public async Task GetAllUserProfilesAsync()
 {
 var userProfiles = new UserProfile[] { new UserProfile { Id = "user1", Username = "user1" } };
 Mock<DbSet<UserProfile>> userDbSetMock = DbSetMock.Create(userProfiles);

 var mockContext = new Mock<ApiFpn2DbContext>(_options);
 mockContext.Setup(c => c.UserProfiles).Returns(userDbSetMock.Object);

 var service = new UserProfileRepository(mockContext.Object);

 var results = await service.GetAsync();
 var data = results.Data;
 Assert.NotNull(data);
 Assert.Equal(1, data?.Count);
 Assert.True(results.Success);
 Assert.Equal(userProfiles[0].Username, data[0].Username);
 }

 }

 

After all that, I found this Github Issue Report for Entity Framework: Async MOQ testing with EntityFramework Core.  I was wondering why EF did not have better testing helpers, as the ones above based on Armen Shimoon’s code.  Ultimately, the EF team does not seem to recommend mocking EF.    So, how then do you test Repositories?  They argue that EF uses a repository pattern already, so why add a second one on top?

Julie Lehrman has a Pluralsight video Entity Framework in the Enterprise: Understanding EF Encapsulation and the great Repository Debates,  in which she reviews the architecture patterns, and when to use a Repository.  My understanding from this video series, is that she will still, for certain simple cases, use a classic repository (basic CRUD functionality).  This mapping is often done via generic repository classes.

However, she seems to highlight business use case specific persistence classes/methods (e.g. Read users with associated Notes), instead of writing  simple wrappers around EF CRUD  (1:1  maps of Create, Read, Update and Delete between the repository and EF).   This makes sense – why recreate what EF already does. She makes a similar argument regarding Unit Of Work objects; why recreate logic already contained in the EF data context object.

Finally, as a related concept, Julie Lehrman says she tends to return IEnumerable from her persistence objects instead of IQueryable, in order to lock down functionality and business logic for specific cases.  Instead of broad reuse (all but the kitchen sink), the focus is on solving specific real needs in the software.  When new use cases arise, write new persistence classes/methods.

I think my repositories have already morphed into business specific logic, rather than simply wrapping EF Crud functionality.  Even if we call these classes something other than Repositories, they are still persistence objects that are not simply EF wrappers and have unique functionality that should be unit tested.  So again, how do you unit test persistence objects, if you do not mock Entity Framework?  I may be misunderstanding the EF team’s response in this Github Issue Report for Entity Framework:, but counter to their argument, I think there is a strong use case for making this testing less cumbersome.

 

Advertisements
This entry was posted in Uncategorized. Bookmark the permalink.