How to Unit Test with ActionResult<T>?

At run time your original code under test would still work because of the implicit conversion.

But based on the provided debugger image it looks like the test was asserting on the wrong property of the result.

So while changing the method under test allowed the test to pass, it would have worked when run live either way

ActioResult<TValue> has two properties that are set depending on what is returned from the action that uses it.

/// <summary>
/// Gets the <see cref="ActionResult"/>.
/// </summary>
public ActionResult Result { get; }

/// <summary>
/// Gets the value.
/// </summary>
public TValue Value { get; }

Source

So when the controller action returned using Ok() it would set the ActionResult<int>.Result property of the action result via implicit conversion.

public static implicit operator ActionResult<TValue>(ActionResult result)
{
    return new ActionResult<TValue>(result);
}

But the test was asserting the Value property (refer to image in OP), which in this case was not being set.

Without having to modify the code under test to satisfy the test it could have accessed the Result property and make assertions on that value

[Fact]
public async Task GetLocationsCountAsync_WhenCalled_ReturnsLocationsCount() {
    //Arrange
    _locationsService
        .Setup(_ => _.GetLocationsCountAsync(It.IsAny<string>()))
        .ReturnsAsync(10);
    var controller = new LocationsController(_locationsService.Object, null) {
        ControllerContext = { HttpContext = SetupHttpContext().Object }
    };

    //Act
    var actionResult = await controller.GetLocationsCountAsync();

    //Assert
    var result = actionResult.Result as OkObjectResult;
    result.Should().NotBeNull();
    result.Value.Should().Be(10);

    VerifyAll();
}

The issue is wrapping it in Ok. If you return the object itself, Value is populated correctly.

If you look at Microsoft's examples in the docs, they only use the controller methods for non-default responses like NotFound:

[HttpGet("{id}")]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult<Product> GetById(int id)
{
    if (!_repository.TryGetProduct(id, out var product))
    {
        return NotFound();
    }

    return product;
}

The problem lies in the confusing interface of ActionResult<T> that was never designed to be used by us humans. As stated in other answers, ActionResult<T> has either its Result or Value property set but not both. When you return an OkObjectResult the framework populates the Result property. When you return an object the framework populates the Value property.

I created the following simple helper for my test library to help me test the return values when I am using OkObjectResult Ok() or other results inheriting from ObjectResult

private static T GetObjectResultContent<T>(ActionResult<T> result)
{
    return (T) ((ObjectResult) result.Result).Value;
}

This lets me do the following:

var actionResult = await controller.Get("foobar");
Assert.IsType<OkObjectResult>(actionResult.Result);
var resultObject = GetObjectResultContent<ObjectType>(actionResult);
Assert.Equal("foobar", resultObject.Id);