我正在尝试模拟多个DbSets和它们的DbContext。 我对这个问题感到很困惑。 我感谢任何反馈。
当我运行测试时,我收到了以下空引用异常。
System.NullReferenceException : Object reference not set to an instance of an object.
在测试方法中,这里抛出了异常。
return enterprisePubContext.WrWaterUsage
.Include(wu => wu.WaterUseCodeDescription)
.Include(wu => wu.PhysicalRight)
.ThenInclude(p => p.WaterRight)
.FirstOrDefault(wu => wu.PlaceOfUseId == placeOfUseId && wu.PhysicalRight.WaterRight != null);
然而,问题似乎并不在于从测试方法返回结果的查询。 问题似乎发生在设置每个模拟DbSet的过程中,由于对模拟DbSets的设置失败,那么对模拟DbContext的设置随后也失败了,似乎一个空的上下文被传递给了测试方法。 我之前使用moq for DbSets,从来没有遇到过这个问题。
当我调试测试时,我看到每个DbSet和每个 Mock<IQueryable<entity>>.Setup<IQueryProvider>
(例如: mockPhysicalRights.As<IQueryable<WrPhysicalRight>>().Setup(m => m.Provider).Returns(physicalRights.Provider)
):
'((Microsoft.EntityFrameworkCore.Infrastructure.IInfrastructure)((Moq.Mock)(mockPhysicalRights)).Object).Instance'抛出一个类型为'System.NotImplementedException'的异常'((System.Linq.IQueryable)((Moq.Mock)('mockDbSet')).Object).Provider'引发了一个类型为'System.NotImplementedException'的异常'((Microsoft.EntityFrameworkCore.Infrastructure.IInfrastructure)((Moq.Mock)(mockPhysicalRights)).Object)).Instance'引发了一个类型为'System.NotImplementedException'的异常'((System.Linq.IQueryable)((Moq.Mock)()).Instance)。mockDbSet)).Object).Expression'引发了一个类型为'System.NotImplementedException'((Microsoft.EntityFrameworkCore.Infrastructure.IInfrastructure)((Moq.Mock)(mockPhysicalRights)).Object).Instance'的异常。mockDbSet)).Object).ElementType'引发了一个类型为'System.NotImplementedException'的异常。
下面是调试模式下的异常截图示例。
同样的异常发生在 mockPhysicalRights.As<IQueryable<WrPhysicalRight>>().Setup(m => m.Provider).Returns(physicalRights.Provider)
也被抛出。
mockPhysicalRights.As<IQueryable<WrPhysicalRight>>()
.Setup(m => m.Expression).Returns(physicalRights.Expression);
'((System.Linq.IQueryable)((Moq.Mock)(mockPhysicalRights)).Object).Expression'抛出一个类型为'System.NotImplementedException'的异常。
mockPhysicalRights.As<IQueryable<WrPhysicalRight>>()
.Setup(m => m.ElementType).Returns(physicalRights.ElementType);
'((System.Linq.IQueryable)((Moq.Mock)(mockPhysicalRights)).Object).ElementType'引发了一个类型为'System.NotImplementedException'的异常。
设置DbContext时。
var mockEnterprisePubContext = new Mock<EnterprisePubContext>();
mockEnterprisePubContext.Setup(m => m.WrPhysicalRight)
.Returns(mockPhysicalRights.Object);
我看到以下错误。
错误CS0103: 名字'm'在当前上下文中不存在。
下面是调试模式下的截图。
希望我已经包含了下面所有的相关代码。
DbContext--为了简单起见,假设不相关的代码被省略了。
public class EnterprisePubContext : DbContext
{
public EnterprisePubContext()
{
}
public EnterprisePubContext(DbContextOptions<EnterprisePubContext> options) : base(options)
{
}
public virtual DbSet<WrPhysicalRight> WrPhysicalRight { get; set; }
public virtual DbSet<WrWaterUsage> WrWaterUsage { get; set; }
public virtual DbSet<WrWaterUseCode> WrWaterUseCode { get; set; }
public virtual DbSet<WrWaterRight> WrWaterRight { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<WrPhysicalRight>(entity =>
{
entity.HasKey(e => e.RightId)
.HasName("wrPhysicalRightPK")
.IsClustered(false);
entity.ToTable("wrPhysicalRight", "dbo");
entity.Property(e => e.RightId).HasColumnName("RightID");
entity.HasOne<WrWaterRight>(pr => pr.WaterRight)
.WithOne(wr => wr.PhysicalRight)
.HasForeignKey<WrWaterRight>(wr => wr.RightId);
});
modelBuilder.Entity<WrWaterRight>(entity =>
{
entity.HasKey(e => new { e.BasinNumber, e.SequenceNumber, e.SplitSuffix })
.HasName("wrWaterRightPK")
.IsClustered(false);
entity.ToTable("wrWaterRight", "dbo");
entity.Property(e => e.SplitSuffix)
.HasMaxLength(2)
.IsUnicode(false);
entity.Property(e => e.Basis)
.HasMaxLength(100)
.IsUnicode(false);
entity.Property(e => e.RightId).HasColumnName("RightID");
entity.Property(e => e.Status)
.HasMaxLength(100)
.IsUnicode(false);
});
modelBuilder.Entity<WrWaterUsage>(entity =>
{
entity.HasKey(e => new { e.WaterUseCode, e.RightId })
.HasName("wrWaterUsagePK")
.IsClustered(false);
entity.ToTable("wrWaterUsage", "dbo");
entity.Property(e => e.WaterUseCode)
.HasMaxLength(2)
.IsUnicode(false);
entity.Property(e => e.RightId).HasColumnName("RightID");
entity.Property(e => e.LargePou).HasColumnName("LargePOU");
entity.Property(e => e.PlaceOfUseId).HasColumnName("PlaceOfUseID");
entity.HasOne<WrPhysicalRight>(usage => usage.PhysicalRight)
.WithMany(pr => pr.WaterUsages)
.HasForeignKey(usage => usage.RightId)
.IsRequired();
entity.HasOne<WrWaterUseCode>(usage => usage.WaterUseCodeDescription)
.WithMany(code => code.Usages)
.HasForeignKey(usage => usage.WaterUseCode)
.IsRequired();
});
modelBuilder.Entity<WrWaterUseCode>(entity =>
{
entity.HasKey(e => e.WaterUseCode)
.HasName("wrWaterUseCodePK")
.IsClustered(false);
entity.ToTable("wrWaterUseCode", "dbo");
entity.Property(e => e.WaterUseCode)
.HasMaxLength(2)
.IsUnicode(false)
.ValueGeneratedNever();
entity.Property(e => e.Description)
.HasMaxLength(255)
.IsUnicode(false);
});
}
}
DbSets - 假设为简单起见,省略了不相关的代码。
public class WrPhysicalRight
{
[Key]
public int RightId { get; set; }
[ForeignKey("RightId")]
public IList<WrWaterUsage> WaterUsages { get; set; }
public WrWaterRight WaterRight { get; set; }
}
public class WrWaterUsage
{
public string WaterUseCode { get; set; }
public int RightId { get; set; }
public int? PlaceOfUseId { get; set; }
public double? TotalAcres { get; set; }
public double? AcreLimit { get; set; }
public bool LargePou { get; set; }
public WrPhysicalRight PhysicalRight { get; set; }
public WrWaterUseCode WaterUseCodeDescription { get; set; }
}
public class WrWaterUseCode
{
public string WaterUseCode { get; set; }
public string Description { get; set; }
[ForeignKey("WaterUseCode")]
public IList<WrWaterUsage> Usages { get; set; }
}
public class WrWaterRight
{
[Key]
public int BasinNumber { get; set; }
[Key]
public int SequenceNumber { get; set; }
[Key]
public string SplitSuffix { get; set; }
public int? RightId { get; set; }
public string Status { get; set; }
public string Basis { get; set; }
[ForeignKey("RightId")]
public WrPhysicalRight PhysicalRight { get; set; }
}
测试方法--为简单起见,假设省略了不相关的代码。
public abstract class Process : Enumeration<string>
{
protected readonly List<string> Aliases = new List<string>();
private Process(int value, string displayName, IEnumerable<string> aliases) : base(value,
displayName)
{
Aliases.Add(displayName);
Aliases.AddRange(aliases);
}
public static readonly Process WaterRight = new WaterRightProcess();
public static WrWaterUsage WaterUsageByPlaceOfUseId(EnterprisePubContext enterprisePubContext,
int placeOfUseId, ref Process process)
{
if (process != null)
return process.GetWaterUsageByPlaceOfUseId(enterprisePubContext, placeOfUseId);
return null;
}
private class WaterRightProcess : Process
{
internal WaterRightProcess() : base(0, "WaterRight", new [] { "right" })
{
}
protected override WrWaterUsage GetWaterUsageByPlaceOfUseId(EnterprisePubContext
enterprisePubContext, int placeOfUseId)
{
//Exception thrown here
return enterprisePubContext.WrWaterUsage
.Include(wu => wu.WaterUseCodeDescription)
.Include(wu => wu.PhysicalRight)
.ThenInclude(p => p.WaterRight)
.FirstOrDefault(wu => wu.PlaceOfUseId == placeOfUseId && wu.PhysicalRight.WaterRight
!= null);
}
}
}
测试
/// <summary>
/// Ensure that water right process returns PlaceOfUse by PlaceOfUseId appropriately
/// </summary>
[Fact]
public void WaterRightProcessReturnsWaterUsageByPlaceOfUseWithMocking()
{
//Mocking physical rights entity
var physicalRights = new List<WrPhysicalRight>
{
new WrPhysicalRight
{
RightId = 1
}
}.AsQueryable();
var mockPhysicalRights = new Mock<DbSet<WrPhysicalRight>>();
mockPhysicalRights.As<IQueryable<WrPhysicalRight>>()
.Setup(m => m.Provider).Returns(physicalRights.Provider);
mockPhysicalRights.As<IQueryable<WrPhysicalRight>>()
.Setup(m => m.Expression).Returns(physicalRights.Expression);
mockPhysicalRights.As<IQueryable<WrPhysicalRight>>()
.Setup(m => m.ElementType).Returns(physicalRights.ElementType);
mockPhysicalRights.As<IQueryable<WrPhysicalRight>>()
.Setup(m => m.GetEnumerator()).Returns(physicalRights.GetEnumerator());
//Mocking water usages entity
var waterUsages = new List<WrWaterUsage>
{
new WrWaterUsage
{
RightId = 1,
WaterUseCode = "01",
PlaceOfUseId = 1,
AcreLimit = 12,
TotalAcres = 12,
LargePou = false
}
}.AsQueryable();
var mockWaterUsages = new Mock<DbSet<WrWaterUsage>>();
mockWaterUsages.As<IQueryable<WrWaterUsage>>()
.Setup(m => m.Provider).Returns(waterUsages.Provider);
mockWaterUsages.As<IQueryable<WrWaterUsage>>()
.Setup(m => m.Expression).Returns(waterUsages.Expression);
mockWaterUsages.As<IQueryable<WrWaterUsage>>()
.Setup(m => m.ElementType).Returns(waterUsages.ElementType);
mockWaterUsages.As<IQueryable<WrWaterUsage>>()
.Setup(m => m.GetEnumerator()).Returns(waterUsages.GetEnumerator());
//Mocking water use codes entity
var waterUseCodes = new List<WrWaterUseCode>
{
new WrWaterUseCode
{
WaterUseCode = "01",
Description = "IRRIGATION"
}
}.AsQueryable();
var mockWaterUseCodes = new Mock<DbSet<WrWaterUseCode>>();
mockWaterUseCodes.As<IQueryable<WrWaterUseCode>>()
.Setup(m => m.Provider).Returns(waterUseCodes.Provider);
mockWaterUseCodes.As<IQueryable<WrWaterUseCode>>()
.Setup(m => m.Expression).Returns(waterUseCodes.Expression);
mockWaterUseCodes.As<IQueryable<WrWaterUseCode>>()
.Setup(m => m.ElementType).Returns(waterUseCodes.ElementType);
mockWaterUseCodes.As<IQueryable<WrWaterUseCode>>()
.Setup(m => m.GetEnumerator()).Returns(waterUseCodes.GetEnumerator());
//Mocking water rights entity
var waterRights = new List<WrWaterRight>
{
new WrWaterRight
{
RightId = 1,
BasinNumber = 63
SequenceNumber = 9874,
SplitSuffix = "",
Status = "Active",
Basis = "Statutory Claim"
}
}.AsQueryable();
var mockWaterRights = new Mock<DbSet<WrWaterRight>>();
mockWaterRights.As<IQueryable<WrWaterRight>>()
.Setup(m => m.Provider).Returns(waterRights.Provider);
mockWaterRights.As<IQueryable<WrWaterRight>>()
.Setup(m => m.Expression).Returns(waterRights.Expression);
mockWaterRights.As<IQueryable<WrWaterRight>>()
.Setup(m => m.ElementType).Returns(waterRights.ElementType);
mockWaterRights.As<IQueryable<WrWaterRight>>()
.Setup(m => m.GetEnumerator()).Returns(waterRights.GetEnumerator());
//Mocking EnterprisePubContext
var mockEnterprisePubContext = new Mock<EnterprisePubContext>();
mockEnterprisePubContext.Setup(m => m.WrPhysicalRight)
.Returns(mockPhysicalRights.Object);
mockEnterprisePubContext.Setup(m => m.WrWaterUsage)
.Returns(mockWaterUsages.Object);
mockEnterprisePubContext.Setup(m => m.WrWaterUseCode)
.Returns(mockWaterUseCodes.Object);
mockEnterprisePubContext.Setup(m => m.WrWaterRight)
.Returns(mockWaterRights.Object);
Process process = Process.WaterRight;
var waterUsage = Process.WaterUsageByPlaceOfUseId(mockEnterprisePubContext.Object, 1, ref
process);
Assert.Equal("01", waterUsage.WaterUseCode);
}
在整理了上面帖子中的思路后,我突然想到,模拟DbSets可能不像创建InMemoryDatabases那样有效。 在尝试模拟测试方法之前,我创建了一个InMemoryDatabase来测试同样的方法,这是创建InMemoryDatabase的代码,成功地用于测试方法。
var options= new DbContextOptionsBuilder<EnterprisePubContext>()
.UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
.EnableSensitiveDataLogging()
.Options;
var databaseContext = new EnterprisePubContext(options);
databaseContext.Database.EnsureCreated();
if (!databaseContext.WrPhysicalRight.Any())
{
databaseContext.WrPhysicalRight.Add(new WrPhysicalRight()
{
RightId = 1
});
}
if (!databaseContext.WrWaterUsage.Any())
{
databaseContext.WrWaterUsage.Add(new WrWaterUsage()
{
RightId = 1,
WaterUseCode = "01",
PlaceOfUseId = 1,
AcreLimit = 12,
TotalAcres = 12,
LargePou = false
});
}
if (!databaseContext.WrWaterUseCode.Any())
{
databaseContext.WrWaterUseCode.Add(new WrWaterUseCode()
{
WaterUseCode = "01",
Description = "IRRIGATION"
});
}
if (!databaseContext.WrWaterRight.Any())
{
databaseContext.WrWaterRight.Add(new WrWaterRight()
{
RightId = 1,
BasinNumber = 63,
SequenceNumber = 9874,
SplitSuffix = "",
Status = "Active",
Basis = "Statutory Claim"
});
}
这个实体初始化对于创建InMemoryDatabase来说,效果不错。 显然,InMemoryDatabase隐式形成了关系。 Moq在模拟DbSets时不会隐式形成关系。 我必须初始化关系属性才能使模拟DbSets工作。
//Mocking physical rights entity
var physicalRights = new List<WrPhysicalRight>
{
new WrPhysicalRight
{
RightId = 1,
WaterRight = new WrWaterRight
{
RightId = 1,
BasinNumber = 63,
SequenceNumber = 9874,
SplitSuffix = "",
Status = "Active",
Basis = "Statutory Claim"
}
}
}.AsQueryable();
//Mocking water usages entity
var waterUsages = new List<WrWaterUsage>
{
new WrWaterUsage
{
RightId = 1,
WaterUseCode = "01",
PlaceOfUseId = 1,
AcreLimit = 12,
TotalAcres = 12,
LargePou = false,
PhysicalRight = new WrPhysicalRight
{
RightId = 1,
WaterRight = new WrWaterRight
{
RightId = 1,
BasinNumber = 63,
SequenceNumber = 9874,
SplitSuffix = "",
Status = "Active",
Basis = "Statutory Claim"
}
},
WaterUseCodeDescription = new WrWaterUseCode
{
WaterUseCode = "01",
Description = "IRRIGATION"
}
}
}.AsQueryable();
//Mocking water use codes entity
var waterUseCodes = new List<WrWaterUseCode>
{
new WrWaterUseCode
{
WaterUseCode = "01",
Description = "IRRIGATION",
Usages = new List<WrWaterUsage>
{
new WrWaterUsage
{
RightId = 1,
WaterUseCode = "01",
PlaceOfUseId = 1,
AcreLimit = 12,
TotalAcres = 12,
LargePou = false,
PhysicalRight = new WrPhysicalRight
{
RightId = 1,
WaterRight = new WrWaterRight
{
RightId = 1,
BasinNumber = 63,
SequenceNumber = 9874,
SplitSuffix = "",
Status = "Active",
Basis = "Statutory Claim"
}
},
WaterUseCodeDescription = new WrWaterUseCode
{
WaterUseCode = "01",
Description = "IRRIGATION"
}
}
}
}
}.AsQueryable();
//Mocking water rights entity
var waterRights = new List<WrWaterRight>
{
new WrWaterRight
{
RightId = 1,
BasinNumber = 63,
SequenceNumber = 9874,
SplitSuffix = "",
Status = "Active",
Basis = "Statutory Claim",
PhysicalRight = new WrPhysicalRight
{
RightId = 1,
WaterRight = new WrWaterRight
{
RightId = 1,
BasinNumber = 63,
SequenceNumber = 9874,
SplitSuffix = "",
Status = "Active",
Basis = "Statutory Claim"
}
}
}
}.AsQueryable();
显式地将物理权与水权、水使用权与物理权和水使用代码、水使用代码与水使用权、水权与物理权联系起来,这对于正确创建模拟上下文是必要的,该方法用来查询所有这些模拟DbSets。 不知道mocking与InMemoryDatabases在幕后发生了什么区别,但显式创建与mocking的关系是requireed。