如何从 [TestMethod] 调用异步?

Posted

技术标签:

【中文标题】如何从 [TestMethod] 调用异步?【英文标题】:How to call async from [TestMethod]? 【发布时间】:2016-07-09 22:57:00 【问题描述】:

我在 WebAPI MVC 项目中有一个相当复杂的方法。它做了很多事情,包括访问远程服务器进行用户身份验证。根据此结果,它会返回重定向(页面)、字符串错误消息或表示所有身份验证猴子业务的对象。

从浏览器调用时效果很好。它甚至在调试中也能正常工作。我什至可以写一个重定向并“手动”调用它,传入它需要的任何参数。

我遇到的问题是从我创建 WebAPI 项目时创建的测试项目 VS 调用它。我怀疑这是因为所有asyncawait 都被扔掉了。当它进入它时,最终它会返回“对象未设置为对象的实例”错误。

由于它适用于任何其他环境,我认为这是因为它在测试项目中并且需要等待。任何人都可以给我任何建议吗?

编辑:具体来说,这里的第二行代码失败了:

BoxConfig boxConfig = new BoxConfig(ClientID, ClientSecret, enterpriseID, prvt, JWTPublicKeyPass, JWTPublicKeyID);
BoxJWTAuth boxJWT = new BoxJWTAuth(boxConfig); //This is a JSON web token and is needed to authorize the enterprise level app user.

代码上下文:

这是利用 Box.com API。 BoxJWT 调用创建一个 JSON Web 令牌。我不知道它在哪里失败,因为当我追踪它时,它无法向我显示 PEMReader.cs 等内容的代码(与加密货币、充气城堡有关)。但非常具体,错误消息详细信息说来源是Box.V2.JWTAuth.PEMPasswordFinder.GetPassword()

【问题讨论】:

现在你的问题太抽象了。请包含您的代码的Minimal, Complete, and Verifiable example。 重复? ***.com/questions/36148778/… 听起来您需要将方法分解为更小的单元并单独测试每个单元。 @Igor:我一上班就会做 【参考方案1】:

每当我创建一个测试异步代码的测试方法时,我都会确保测试方法的签名是 Async 并返回 Task。这使您可以避免来自 async void 的可怕死锁。现在您可以在测试方法中等待。

更多信息:https://msdn.microsoft.com/en-us/magazine/dn818493.aspx

例子:

[TestMethod]
public async Task myTestMethod()

  await MyMethodToTest();

【讨论】:

唉,这实际上并没有做任何有用的事情,因为测试项目是一个 WebAPI 服务并且 Get 语句具有参数。 [TestMethod] 不能是异步的,也不能有参数(即使编译器在输出中也这样说)。我不得不以艰难的方式找到这一点。无论如何,很高兴知道,即使这不是问题的原因。 (bit.ly/1pIdbqU)【参考方案2】:

这是Moq 框架的一个很好的示例用例。例如,当您提请注意进行远程调用时,应该模拟该服务。理想情况下,您应该尝试模拟不同的变量方面,以便您可以轻松且一致地提供一组已知的输入来验证预期的一组输出。由于您没有提供任何源代码,我将做一些假设——分享一些示例代码和相应的单元测试。

public class LoginResult

    public string RedirectUrl  get; set; 
    public string FailureMsg  get; set; 
    public MonkeyBusiness AuthToken  get; set; 


[
    HttpPost,
    AllowAnonymous,
    Route("api/[controller]/login")
]
public async Task<LoginResult> Login(
    [FromBody] CredentialsModel credentials,
    [FromServices] ILogger logger,
    [FromServices] IAuthenticationModule authentication,
    [FromServices] IAuthClaimsPrincipalBuiler claimsPrincipalBuilder)

    try
    
        var result = 
            await authentication.ExternalAuthenticateAsync(credentials);

        if (result.IsAuthenticated)
        
            var principal = 
                await claimsPrincipalBuilder.BuildAsync(credentials);
            await HttpContext.Authentication.SignInAsync("Scheme", principal);
            return new LoginResult 
            
                AuthToken = 
                    "Too much source to make up just for ***, " +
                    "but I hope you now get the point..."
            ;
        

        return new LoginResult  FailureMsg = "Invalid credentials." ;
    
    catch (Exception ex)
    
        logger.LogError(ex.Message, ex);
        return new LoginResult  FailureMsg = ex.Message ;
    

然后您可以进行与此类似的单元测试,在其中模拟接口的特定实现。在这种情况下,您将模拟IAuthenticationModule 并进行多项测试,以确保无论远程服务器返回您的Login 逻辑如何都按期望/预期处理它。模拟async 的东西很容易做到。

using xUnit;

[Fact]
public async Task LoginThrowsAsExpectedTest()
   
    // Arrange     
    var controller = new LoginController();
    var loggerMock = new Mock<ILogger>();
    var authModuleMock = new Mock<IAuthenticationModule>();

    var expectedMessage = "I knew it!";       

    // Setup async methods! 
    authModuleMock.Setup(am => am.ExternalAuthenticateAsync(It.IsAny<CredentialsModel>())
                  .ThrowsAsync(new Exception(expectedMessage));

    // Act
    var result = 
        await controller.Login(
            new CredentialsModel(),
            loggerMock.Object,
            authModuleMock.Object,
            null);


    // Assert
    Assert.Equal(expectedMessage, result.FailureMsg);

或者您希望正确验证的一个。

using xUnit;

[Fact]
public async Task LoginHandlesIsNotAuthenticatedTest()
   
    // Arrange     
    var controller = new LoginController();
    var loggerMock = new Mock<ILogger>();
    var authModuleMock = new Mock<IAuthenticationModule>();        

    var expectedMessage = "Invalid credentials."; 

    // Setup async methods! 
    authModuleMock.Setup(am => am.ExternalAuthenticateAsync(It.IsAny<CredentialsModel>())
                  .ReturnsAsync(new AuthResult  IsAuthenticated = false );        

    // Act
    var result = 
        await controller.Login(
            new CredentialsModel(),
            loggerMock.Object,
            authModuleMock.Object,
            null);


    // Assert
    Assert.Equal(expectedMessage, result.FailureMsg);

关键是您仍然可以相当轻松地使用 Moq 编写 asyncawait 单元测试...

【讨论】:

这不是问题,但这是非常很好的信息,我将实施它。谢谢!【参考方案3】:

在这个非常具体的例子中,信息是在运行时从 web.config 文件中提取的。

当测试项目尝试运行时,“web.config”的上下文会发生变化,因此测试项目的 AppSettings 中的任何自定义信息也必须在测试项目的 App.Config 文件中。这种行为与连接字符串的安全实践一致,因此不足为奇。

将此信息添加到测试项目的 App.Config 文件已解决问题。

【讨论】:

【参考方案4】:

您可以使用 GetAwaiter 和 GetResult,其中 Execute 是您要测试的异步方法。

var mockParam = new Mock<IParam>();
var sut = new MyClass(mockParam);
var result = sut.Execute().GetAwaiter().GetResult();

您可以在此处找到更多信息。 https://msdn.microsoft.com/en-us/magazine/dn818493.aspx?f=255&MSPPError=-2147217396。它简要提到了您可以测试它的其他方法,但这种方法可以为您提供最准确的结果。

【讨论】:

如果您这样做,您实际上并没有异步执行任何工作,这意味着它的功能与实际代码不同,这对测试来说是个问题。 这是在 async / await 导致问题的情况下,因为某些第三方框架不支持异步单元测试。在正常情况下 async / await 可以做同样的事情。 如果您的测试框架不支持异步代码(请注意,您实际上并不知道 OP 的框架不支持异步方法),那么解决方案是 获取一个框架支持异步方法,如果你不这样做,你根本就没有测试你的代码,因此不会从这些测试中受益。

以上是关于如何从 [TestMethod] 调用异步?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 EasyMock 测试 void 方法

如何从非异步方法调用异步方法? [复制]

如何从异步调用返回响应

如何从异步调用返回响应

如何从异步调用返回响应

如何从异步调用返回响应