Advertisement
benjaminvr

Generic in-memory data provider for DbContext using FakeDbSet, AutoFixture, Moq, EF Core

Jul 5th, 2022 (edited)
773
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C# 3.35 KB | None
  1. /*
  2.  * Benjamin Van Renterghem
  3.  * https://benjaminvr.net
  4.  *
  5.  To be used together with FakeDbSet -- https://pastebin.com/anNZNUZQ
  6.  Allows for quick population/generation of objects, stored in fakedbsets in memory
  7.  No database, no migrations -- great for testing purposes
  8.  */
  9.  
  10. using AutoFixture;
  11. using Datalayer.Model.Abstract;
  12. using Microsoft.EntityFrameworkCore;
  13. using Moq;
  14. using System.Linq.Expressions;
  15. using System.Reflection;
  16.  
  17. namespace Datalayer;
  18.  
  19. // Ideally, this is exported into its own class
  20. public static class DbContextExtensions {
  21.  
  22.     // The DbSet properties are extracted from a dbcontext
  23.     public static List<PropertyInfo> GetDbSetProperties(this DbContext context) {
  24.         var dbSetProperties = new List<PropertyInfo>();
  25.  
  26.         foreach (var property in context.GetType().GetProperties()) {
  27.             var propertyType = property.PropertyType;
  28.             if (propertyType.IsGenericType && typeof(DbSet<>).IsAssignableFrom(propertyType.GetGenericTypeDefinition())) {
  29.                 dbSetProperties.Add(property);
  30.             }
  31.         }
  32.  
  33.         return dbSetProperties;
  34.     }
  35. }
  36.  
  37. public class Provider<T> where T : DbContext {
  38.     private static Fixture _fixture = new();
  39.     private const int DefaultAmtOfGenerations = 30;
  40.     private static Mock<T> _context = new();
  41.     private bool InitialDataGenerated { get; set; } = false;
  42.  
  43.     // Generates fixtures (object instances with random data)
  44.     public static List<T2> GenerateFixtures<T2>(int amount = DefaultAmtOfGenerations) {
  45.         return _fixture.CreateMany<T2>(amount).ToList();
  46.     }
  47.  
  48.     // Leverages fixture generation to create and populate a new FakeDbSet
  49.     public static FakeDbSet<T2> CreatePopulatedFakeDbSet<T2>() where T2 : class {
  50.         return new FakeDbSet<T2>(GenerateFixtures<T2>());
  51.     }
  52.  
  53.     // Provides a dbcontext mock with FakeDbSets set
  54.     public Mock<T> GetDbContextMock (bool getSharedContext=true, bool forceRefreshData=false) {
  55.         Mock<T> mock = getSharedContext ? _context : new();
  56.         List<PropertyInfo> dbSetProperties = _context.Object.GetDbSetProperties();
  57.  
  58.         // If data has been generated before & there's no intent to replace it, current state is returned
  59.         if(InitialDataGenerated && !forceRefreshData) {
  60.             return mock;
  61.         }
  62.  
  63.         // For each dbset in the dbcontext, we set the FakeDbSet using SetupGet at its property
  64.         foreach (PropertyInfo dbSetProperty in dbSetProperties) {
  65.             Type dbSetType = dbSetProperty.PropertyType;
  66.             Type entityType = dbSetProperty.PropertyType.GetGenericArguments().ToList().FirstOrDefault()
  67.                               ?? throw new ArgumentNullException();
  68.  
  69.             ParameterExpression @this = Expression.Parameter(typeof(Provider<T>), "this");
  70.             ParameterExpression dbContextParam = Expression.Parameter(typeof(T), "db");
  71.             MemberExpression accessDbSet = Expression.PropertyOrField(dbContextParam, dbSetProperty.Name);
  72.  
  73.             var accessor = Expression.Lambda<Func<T, dynamic>>(
  74.                 accessDbSet,
  75.                 dbContextParam
  76.             );
  77.  
  78.             // Create a FakeDbSet
  79.             var call = Expression.Call(typeof(Provider<T>), "CreatePopulatedFakeDbSet", new Type[] { entityType });
  80.             var dbset = Expression.Lambda<Func<Provider<T>, dynamic>>(call, @this).Compile().Invoke(this);
  81.  
  82.             // Appoint to the property
  83.             mock.SetupGet(accessor).Returns(dbset);
  84.         }
  85.  
  86.         InitialDataGenerated = true;
  87.         return mock;
  88.     }
  89.  
  90.     public T GetDbContext(bool getSharedContext=true, bool forceRefreshData=false) {
  91.         return GetDbContextMock(getSharedContext, forceRefreshData).Object;
  92.     }
  93. }
  94.  
Advertisement
RAW Paste Data Copied
Advertisement