我正在建立一个多租户网站,因此我需要重载UserManager.CreateAsync()
以接受type的其他参数City
。换句话说,每个都User
属于City
。我已经在User
模型上配置了额外的属性。
问题是我无法让Moq为重载的模拟方法引发回调。
这是我的模拟设置:
Protected Function GetUserManagerMock(Of TUser As Db.User)(Users As List(Of TUser), ExpectedResult As IdentityResult) As Mock(Of UserManager)
Dim oCreateSetup As Expression(Of Func(Of UserManager, Task(Of IdentityResult)))
Dim oDeleteSetup As Expression(Of Func(Of UserManager, Task(Of IdentityResult)))
Dim oUpdateSetup As Expression(Of Func(Of UserManager, Task(Of IdentityResult)))
Dim oManagerMock As Mock(Of UserManager)
Dim oStoreMock As Mock(Of IUserStore(Of TUser))
Dim oCallback As Action(Of TUser, String, Db.City)
Dim oManager As UserManager
Dim oResult As IdentityResult
oStoreMock = New Mock(Of IUserStore(Of TUser))
oManagerMock = New Mock(Of UserManager)(oStoreMock.Object)
oManager = oManagerMock.Object
oCallback = Sub(User, Password, City)
oResult = oManager.PasswordValidator.ValidateAsync(Password).Result
If oResult Is IdentityResult.Success Then
User.PasswordHash = oManager.PasswordHasher.HashPassword(Password)
Users.Add(User)
User.CityId = City.Id
User.City = City
City.Users.Add(User)
End If
End Sub
oCreateSetup = Function(Manager) Manager.CreateAsync(It.IsAny(Of TUser), It.IsAny(Of String), It.IsAny(Of Db.City))
oDeleteSetup = Function(Manager) Manager.DeleteAsync(It.IsAny(Of TUser))
oUpdateSetup = Function(Manager) Manager.UpdateAsync(It.IsAny(Of TUser))
oManagerMock.Setup(oCreateSetup).ReturnsAsync(ExpectedResult).Callback(oCallback)
oManagerMock.Setup(oDeleteSetup).ReturnsAsync(ExpectedResult)
oManagerMock.Setup(oUpdateSetup).ReturnsAsync(ExpectedResult)
Return oManagerMock
End Function
这是重载的UserManager
方法:
Public Overridable Overloads Async Function CreateAsync(User As Db.User, Password As String, City As Db.City) As Task(Of IdentityResult)
Dim oResult As IdentityResult
Password.ThrowIfNothing(NameOf(Password))
User.ThrowIfNothing(NameOf(User))
City.ThrowIfNothing(NameOf(City))
User.CityId = City.Id
User.City = City
oResult = Await MyBase.CreateAsync(User, Password)
If oResult.Succeeded Then
City.Users.RemoveAll(Function(U) U.Id = User.Id)
City.Users.Add(User)
Else
User.CityId = Nothing
User.City = Nothing
End If
Return oResult
End Function
这是被测试的控制器方法:
<HttpPost>
<ActionName("Create")>
<AllowAnonymous>
<ValidateAntiForgeryToken>
Public Async Function CreateAsync(Model As Welcome) As Task(Of ActionResult)
Dim oIdentity As IdentityResult
Dim oAction As ActionResult
Dim oUser As Db.User
If Me.ModelState.IsValid Then
If Model.City.IsNothing Then
Model.City = Me.TempData.Peek(NameOf(Db.City))
oAction = Me.View("One", Model)
Else
oUser = New Db.User With {
.FirstName = Model.FirstName,
.LastName = Model.LastName,
.UserName = Model.Username,
.CityId = Model.City.Id,
.City = Model.City
}
oIdentity = Await Me.UserManager.CreateAsync(oUser, Model.Password, Model.City)
If oIdentity.Succeeded Then
oAction = Me.View("Two", Model)
Else
oAction = Me.View("Three", Model)
End If
End If
Else
oAction = Me.View("Four", Model)
End If
Return oAction
End Function
...这是测试:
<Fact>
Public Async Function CreateUserSucceeds() As Task
Dim oUserManager As UserManager
Dim oController As UsersController
Dim oViewModel As Welcome
Dim oResult As ViewResult
Dim oCity As Db.City
Me.Users.Clear()
Me.Cities.ForEach(Sub(City) City.Users.Clear())
oCity = Me.Cities.First
oUserManager = Me.UserManagerMock(Me.Users, IdentityResult.Success).Object
oController = New UsersController(Me.Worker, Nothing, oUserManager, Nothing)
oViewModel = New Welcome With {.City = oCity, .FirstName = "User", .LastName = "Name", .Password = "P@ssw0rd!"}
oResult = TryCast(Await oController.CreateAsync(oViewModel), ViewResult)
Assert.True(Me.Users.Count = 1)
Assert.True(Me.Users.First.City.IsNotNothing)
Assert.True(oCity.Code = Me.Users.First.City.Code)
End Function
执行UserManager.CreateAsync()
将按预期方式跳过重载的方法,但回调Action
永远不会运行。似乎完全跳过了控制器方法中的这一行代码:
oIdentity = Await Me.UserManager.CreateAsync(oUser, Model.Password, Model.City)
不管我做什么,oIdentity
都是这样Nothing
(null
在C#中)。
我试着在CallBase
打开时创建模拟,如下所示:
oManagerMock = New Mock(Of UserManager)(oStoreMock.Object) With {.CallBase = True}
...但是导致重载的方法本身正在运行,这当然不会。我正在运行单元测试,而不是集成测试。
谁能发现为什么没有调用此回调?
-编辑1--
从那以后,我发现不再为我模拟的任何方法引发回调,而不仅仅是重载方法。与过去一样,这非常奇怪。
我已经决定从备份中还原代码,并将其恢复到工作状态。我将尽快获得更多信息。
-编辑2--
问题仍然没有解决,但是我已经接近了。
当我将UserManager
模拟转换为Identity框架类(即UserManager(Of TUser)
)时,将成功引发回调。但是,当我将其强制转换为子类时,为了获取重载的方法,它将失败。
这就是为什么我最初给人的印象是只有重载方法才导致失败-因为我必须更改转换才能模拟该方法。更换铸件是引起问题的原因,而不是过载。
我已经检查了Identity框架的源代码,没有发现与我的概念不同的任何内容。
这是我的完整UserManager
子类:
Imports System
Imports System.Threading.Tasks
Imports Intexx
Imports Website.Models
Imports Microsoft.AspNet.Identity
Imports Microsoft.AspNet.Identity.EntityFramework
Imports Microsoft.AspNet.Identity.Owin
Imports Microsoft.Owin
Imports Microsoft.Owin.Security.DataProtection
Public Class UserManager
Inherits UserManager(Of Db.User)
Public Sub New(Store As IUserStore(Of Db.User))
MyBase.New(Store)
Me.PasswordValidator = New PasswordValidator
Me.UserValidator = New UserValidator(Me)
End Sub
Public Shared Function Create(Options As IdentityFactoryOptions(Of UserManager), Context As IOwinContext) As UserManager
Dim oPhoneProvider As PhoneNumberTokenProvider(Of Db.User)
Dim oEmailProvider As EmailTokenProvider(Of Db.User)
Dim oDataProtector As IDataProtector
Dim oDataProvider As IDataProtectionProvider
Dim oUserStore As IUserStore(Of Db.User)
Dim oManager As UserManager
Dim oContext As Db.Context
oContext = Context.Get(Of Db.Context)
oUserStore = New UserStore(Of Db.User)(oContext)
oManager = New UserManager(oUserStore) With {
.MaxFailedAccessAttemptsBeforeLockout = 5,
.DefaultAccountLockoutTimeSpan = TimeSpan.FromMinutes(5),
.UserLockoutEnabledByDefault = True
}
' Create the two-factor authentication providers
oPhoneProvider = New PhoneNumberTokenProvider(Of Db.User) With {.MessageFormat = "Your security code is {0}"}
oEmailProvider = New EmailTokenProvider(Of Db.User) With {.Subject = "Security Code", .BodyFormat = "Your security code is {0}"}
' Register the two-factor authentication providers. This application
' uses Phone and Email as a step of receiving a code for verifying
' the user. You can write your own provider and plug it in here.
oManager.RegisterTwoFactorProvider("Phone Code", oPhoneProvider)
oManager.RegisterTwoFactorProvider("Email Code", oEmailProvider)
oDataProvider = Options.DataProtectionProvider
If oDataProvider.IsNotNothing Then
oDataProtector = oDataProvider.Create("ASP.NET Identity")
oManager.UserTokenProvider = New DataProtectorTokenProvider(Of Db.User)(oDataProtector)
End If
oManager.EmailService = New EmailService
oManager.SmsService = New SmsService
Return oManager
End Function
Public Overridable Overloads Async Function CreateAsync(User As Db.User, Password As String, City As Db.City) As Task(Of IdentityResult)
Dim oResult As IdentityResult
Password.ThrowIfNothing(NameOf(Password))
User.ThrowIfNothing(NameOf(User))
City.ThrowIfNothing(NameOf(City))
User.CityId = City.Id
User.City = City
oResult = Await MyBase.CreateAsync(User, Password)
If oResult.Succeeded Then
City.Users.RemoveAll(Function(U) U.Id = User.Id)
City.Users.Add(User)
Else
User.CityId = Nothing
User.City = Nothing
End If
Return oResult
End Function
End Class
就目前而言,由于我似乎只能在使用本机强制类型转换时才引发回调,因此一个临时解决方法是使用该强制类型转换并User.City = Nothing
在回调中进行检查。至少我可以阻止它。
但是从长远来看,为了更好的代码稳定性,我想弄清楚为什么当我转换UserManager
为子类时不引发回调。
有任何想法吗?我知道这是一个漫长的过程,但这可能是Moq中的错误吗?
-编辑3--
实际上,这似乎是VB中的错误。它在C#中可以正常工作。
这是一个复制解决方案:OneDrive
我在Moq回购中提出了一个问题,其中提供了一些有关如何使用和调试repro以获得相同结果的详细信息。
由于有了这个新发现,我将VB.NET和C#标记都添加到此问题中。
谁能发现两个项目之间的差异?
欢迎所有建议。
问题出在Mob没想到必须考虑的VB.NET编译器中。
https://github.com/moq/moq4/issues/1067#issuecomment-706671833
对其进行了相应的调整,将在以后的版本中提供。
非常感谢stakx
他出色而迅速的工作。
本文收集自互联网,转载请注明来源。
如有侵权,请联系 [email protected] 删除。
我来说两句