如何从通过 API 返回的 Task<IActionResult> 获取值以进行单元测试

Posted

技术标签:

【中文标题】如何从通过 API 返回的 Task<IActionResult> 获取值以进行单元测试【英文标题】:How to get the Values from a Task<IActionResult> returned through an API for Unit Testing 【发布时间】:2018-06-11 15:20:29 【问题描述】:

我使用 ASP.NET MVC Core v2.1 创建了一个 API。我的HttpGet 方法之一设置如下:

public async Task<IActionResult> GetConfiguration([FromRoute] int? id)

    try
    
        if (!ModelState.IsValid)
        
            return BadRequest(ModelState);
        

        ..... // Some code here

        return Ok(configuration);
    
    catch (Exception ex)
    
        ... // Some code here
    

当对此进行单元测试时,我可以检查 Ok 是响应,但我确实需要查看配置的值。我似乎无法让它与以下内容一起使用:

[TestMethod] 
public void ConfigurationSearchGetTest()

    var context = GetContextWithData();
    var controller = new ConfigurationSearchController(context);
    var items = context.Configurations.Count();
    var actionResult = controller.GetConfiguration(12);

    Assert.IsTrue(true);
    context.Dispose();

在运行时,我可以检查 actionResult 是否具有某些我无法编​​码的值。有什么我做错了吗?还是我只是在想这个错误?我希望能够做到:

Assert.AreEqual(12, actionResult.Values.ConfigurationId);

【问题讨论】:

你应该等待异步方法。 希望您的控制器中没有很多代码实际上需要测试,并且大部分代码都在其他更可测试的类中!请注意,由于您使用的是 ASP.NET MVC Core 2.1,因此您可能希望开始使用 ActionResult&lt;T&gt; 类型。这应该有助于您的测试。 【参考方案1】:

您可以在不更改返回类型的情况下获得经过测试的控制器。IActionResult 是所有其他控制器的基本类型。 将结果转换为预期类型并将返回值与预期值进行比较。

由于您正在测试异步方法,因此也将测试方法设为异步。

[TestMethod] 
public async Task ConfigurationSearchGetTest()

    using (var context = GetContextWithData())
    
        var controller = new ConfigurationSearchController(context);
        var items = context.Configurations.Count();

        var actionResult = await controller.GetConfiguration(12);

        var okResult = actionResult as OkObjectResult;
        var actualConfiguration = okResult.Value as Configuration;

        // Now you can compare with expected values
        actualConfuguration.Should().BeEquivalentTo(expected);
    

【讨论】:

出于测试目的,这似乎是最好的答案 这个 var actualConfiguration = okResult.Value as Configuration;为实际配置赋值??【参考方案2】:

良好的做法是建议您在控制器操作中没有太多代码要测试,并且大部分逻辑位于其他更容易测试的解耦对象中。话虽如此,如果您仍想测试您的控制器,那么您需要进行测试 async 并等待调用。

您将遇到的一个问题是您正在使用IActionResult,因为它允许您返回BadRequest(...)Ok(...)。但是,由于您使用的是 ASP.NET MVC Core 2.1,因此您可能希望开始使用新的 ActionResult&lt;T&gt; 类型。这应该有助于您的测试,因为您现在可以直接访问强类型返回值。例如:

//Assuming your return type is `Configuration`
public async Task<ActionResult<Configuration>> GetConfiguration([FromRoute] int? id)

    try
    
        if (!ModelState.IsValid)
        
            return BadRequest(ModelState);
        

        ..... // Some code here

        // Note we are now returning the object directly, there is an implicit conversion 
        // done for you
        return configuration;
    
    catch (Exception ex)
    
        ... // Some code here
    

注意我们现在直接返回对象,因为有一个implicit conversion from Foo to ActionResult&lt;Foo&gt;

现在您的测试可能如下所示:

[TestMethod] 
public async Task ConfigurationSearchGetTest()

    var context = GetContextWithData();
    var controller = new ConfigurationSearchController(context);
    var items = context.Configurations.Count();

    // We now await the call
    var actionResult = await controller.GetConfiguration(12);

    // And the value we want is now a property of the return
    var configuration = actionResult.Value;

    Assert.IsTrue(true);
    context.Dispose();

【讨论】:

所以,这似乎部分工作,但检查 actionResult.Value 为空。 actionResult.Result.Value.ConfigurationId 可以在运行时看到,但我无法为它编程,因为它不存在。 嗯,你可能只需要返回configuration 而不是Ok(configuration) 是的,我想我可以这样做,但我正在寻找一种方法来获取 ActionResult 以及与请求匹配的配置/配置。 为什么没有更好的文档记录?我花了几个小时才得到返回值。谢谢。 @DavidG 非常感谢你的评论,我被这个愚蠢的错误困了一个小时!【参考方案3】:

由于我的名声不允许我评论@DavidG 的正确方向的答案,我将举例说明如何在Task&lt;IActionResult&gt; 中获取价值。

正如@Christopher J. Reynolds 所指出的,actionResult.Value 可以在运行时看到,但不能在编译看到。

所以,我将展示一个获取Values 的基本测试:

[TestMethod]
public async Task Get_ReturnsAnArea()

    // Arrange
    string areaId = "SomeArea";
    Area expectedArea = new Area()  ObjectId = areaId, AreaNameEn = "TestArea" ;

    var restClient = new Mock<IRestClient>();
    restClient.Setup(client => client.GetAsync<Area>(It.IsAny<string>(), false)).ReturnsAsync(expectedArea);

    var controller = new AreasController(restClient.Object);

    //// Act

    // We now await the call
    IActionResult actionResult = await controller.Get(areaId);

    // We cast it to the expected response type
    OkObjectResult okResult = actionResult as OkObjectResult;

    // Assert

    Assert.IsNotNull(okResult);
    Assert.AreEqual(200, okResult.StatusCode);

    Assert.AreEqual(expectedArea, okResult.Value);

   // We cast Value to the expected type
    Area actualArea = okResult.Value as Area;
    Assert.IsTrue(expectedArea.AreaNameEn.Equals(actualArea.AreaNameEn));

当然这可以改进,但我只是想向您展示一个简单的方法来获得它。

希望对你有帮助。

【讨论】:

将其转换为 OkObjectResult 然后检查状态码是否真的是 200 是否有意义?【参考方案4】:

您需要等待对 GetConfiguration 的调用以获取 IActionResult 对象,如下所示:

var actionResult = await controller.GetConfiguration(12);

为此,您还需要将测试方法的签名也更改为异步。所以改变这个:

public void ConfigurationSearchGetTest()

到这里:

public async Task ConfigurationSearchGetTest()

【讨论】:

【参考方案5】:

如果您需要快速解决方案, 使用 JsonConvert.SerializeObject() ,然后使用 JsonConvert.DeserializeObject() 然后你会得到带有值的对象。

 [TestMethod] 
    public async Task ConfigurationSearchGetTest()
    
        using (var context = GetContextWithData())
        
            var controller = new ConfigurationSearchController(context);
            var items = context.Configurations.Count();
    
            var actionResult = await controller.GetConfiguration(12);
    
            var okResult = actionResult as OkObjectResult;
            var actualConfiguration = okResult.Value ;
  //
  //IMPORTANT ONLY BELOW two lines  need.
  //

var actualConfigurationJStr=JsonConvert.SerializeObject( okResult.Value );
var hereObjectWithStrongType=JsonConvert.DeserializeObject<Configuration>(actualConfigurationJStr);

    
            // Now you can compare with expected values
            actualConfuguration.Should().BeEquivalentTo(expected);
        
    

【讨论】:

以上是关于如何从通过 API 返回的 Task<IActionResult> 获取值以进行单元测试的主要内容,如果未能解决你的问题,请参考以下文章

使用 async/await 并从 ASP.NET Web API 方法返回 Task<HttpResponseMessage>

如何将midi从java程序发送到OSX上的IAC总线

从 asp.net API 中的方法返回后,如何保持线程运行?

你如何设置一些函数在后台线程中运行但保持正常的返回类型而不是 Task<int>?

我如何使用IEnumerable从http调用的json输入中创建变量? C#.NET

从 Task<AuthenticationResult> 转换为 AuthenticationResult