如何从[TestMethod]调用异步?

时间:2016-03-22 20:44:11

标签: c# unit-testing asynchronous asp.net-web-api async-await

我在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()

4 个答案:

答案 0 :(得分:9)

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

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

示例:

[TestMethod]
public async Task myTestMethod()
{
  await MyMethodToTest();
}

答案 1 :(得分:1)

这是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 stackoverflow, " +
                    "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单元测试......

答案 2 :(得分:0)

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

当Test项目尝试运行时,“web.config”的上下文会发生变化,以便测试项目的AppSettings中的任何自定义信息也必须位于Test项目的App.Config文件中。此行为与连接字符串的安全实践一致,因此并不令人惊讶。

将此信息添加到Test Project的App.Config文件已解决该问题。

答案 3 :(得分:-2)

您可以使用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。它简要地提到了你可以测试它的其他方法,但这种方法可以给你最准确的结果。