没有为重载的模拟方法引发回调

InteXX

我正在建立一个多租户网站,因此我需要重载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都是这样Nothingnull在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#标记都添加到此问题中。

谁能发现两个项目之间的差异?

欢迎所有建议。

InteXX

问题出在Mob没想到必须考虑的VB.NET编译器中。

https://github.com/moq/moq4/issues/1067#issuecomment-706671833

对其进行了相应的调整,将在以后的版本中提供。

非常感谢stakx他出色而迅速的工作。

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章

Scalamock 3.没有参数的模拟重载方法

有没有一种方法可以防止在破折号中触发回调?

从具有副作用的类方法引发的模拟异常给出“没有引发”

有没有一种方法可以为重载运算符调用基本方法?C#

没有为String解析方法

重载没有参数的方法

为什么实例初始化块没有为重复输入抛出编译时错误?

如何向下转换为重载方法

异步Task方法后触发回调

重载的模板函数没有为特定类型选择正确的版本

为什么 Angular 没有为 HttpClient.get(...) 选择正确的重载?

如果没有为Gradle提供任何参数,是否有可能引发错误?

没有为连续调度的文件调用AVAudioPlayerNode完成回调

freeglut (./a.out): 错误: 没有为窗口 1 注册显示回调

MVC 6没有为未定义的路由引发异常

没有回调作为参数的模拟功能

没有为“ThemeData”类型定义方法“dark”

没有为“FirebaseAuth”类型定义方法“signInWithAnonymously”

Dart没有为该类定义方法'setRng'

没有为“Type”类型定义方法“getContacts”

没有为“screenutil”颤动定义方法 getInstance()

clojure:没有为类找到协议方法的实现

没有为“功能”类定义“监听”方法吗?

在BeforeAll方法中没有为参数注册ParameterResolver

没有为类型类定义方法

没有为“RenderObject”类型定义方法“globalToLocal”

没有为“NasabahDataTableSource”类型定义方法“setState”

没有为每个新创建的项目显示模拟器

我的程序没有重载方法