我在WebAPI MVC项目中有一个相当复杂的方法。它执行许多操作,包括命中远程服务器以进行用户身份验证。根据结果,它返回重定向(页面),字符串错误消息或表示所有身份验证猴子业务的对象。
从浏览器调用时效果很好。它甚至可以在调试中正常工作。我甚至可以写一个重定向到它并“手动”调用它,传递它需要的任何参数。
我遇到的问题是在创建WebAPI项目时从测试项目VS调用它。我怀疑这是因为所有async
和await
被抛出。当它进入它时,最终它返回一个“对象未设置为对象的实例”错误。
由于它适用于任何其他环境,我认为这是因为它在测试项目中并且需要等待。任何人都可以给我任何建议吗?
编辑:非常具体,这里的第二行代码失败了:
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()
答案 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 编写async
和await
单元测试......
答案 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。它简要地提到了你可以测试它的其他方法,但这种方法可以给你最准确的结果。