【问题标题】:How do I unit test web api action method when it returns IHttpActionResult?返回 IHttpActionResult 时如何对 Web api 操作方法进行单元测试?
【发布时间】:2013-11-25 01:22:37
【问题描述】:

假设这是我的操作方法

public IHttpActionResult Get(int id)
{
    var status = GetSomething(id);
    if (status)
    {
        return Ok();
    }
    else
    {
        return NotFound();
    }
}

测试将是

var httpActionResult = controller.Get(1);

之后如何检查我的 http 状态码?

【问题讨论】:

标签: c# asp.net-web-api asp.net-web-api2


【解决方案1】:

这里Ok() 只是OkResult 类型的助手,它将响应状态设置为HttpStatusCode.Ok...所以您可以检查您的操作结果实例是否为OkResult...一些例子(写在XUnit):

// if your action returns: NotFound()
IHttpActionResult actionResult = valuesController.Get(10);
Assert.IsType<NotFoundResult>(actionResult);

// if your action returns: Ok()
actionResult = valuesController.Get(11);
Assert.IsType<OkResult>(actionResult);

// if your action was returning data in the body like: Ok<string>("data: 12")
actionResult = valuesController.Get(12);
OkNegotiatedContentResult<string> conNegResult = Assert.IsType<OkNegotiatedContentResult<string>>(actionResult);
Assert.Equal("data: 12", conNegResult.Content);

// if your action was returning data in the body like: Content<string>(HttpStatusCode.Accepted, "some updated data");
actionResult = valuesController.Get(13);
NegotiatedContentResult<string> negResult = Assert.IsType<NegotiatedContentResult<string>>(actionResult);
Assert.Equal(HttpStatusCode.Accepted, negResult.StatusCode);
Assert.Equal("some updated data", negResult.Content);

【讨论】:

  • 在 MSTest Assert.IsInstanceOfType(httpActionResult, typeof(OkResult));
  • 另外,对于Created&lt;T&gt;(url,content)CreatedNegotiatedContentResult
  • 谢谢 Sunil.. 可能 Created 不是 Get 操作的好例子...我现在将状态码更改为不同的...
  • @StanimirYakimov 当您将T 类型的对象传递给Ok() 时,结果类型将为OkNegotiatedContentResult&lt;T&gt;
  • 对返回不规则代码的 IHttpStatusCodes 有任何帮助吗?比如422? return new StatusCodeResult((HttpStatusCode)422, this);
【解决方案2】:

是时候复活一个死问题了

当前的答案都依赖于将响应对象转换为已知类型。不幸的是,如果没有对控制器实现的深入了解,响应似乎没有可用的层次结构或隐式转换路径。考虑以下几点:

public class MixedCodeStandardController : ApiController {

    public readonly object _data = new Object();

    public IHttpActionResult Get() {
        return Ok(_data);
    }

    public IHttpActionResult Get(int id) {
        return Content(HttpStatusCode.Success, _data);
    }
}

测试类:

var testController = new MixedCodeStandardController();

var getResult = testController.Get();
var posRes = getResult as OkNegotiatedContentResult<object>;
Assert.IsType<OkNegotiatedContentResult<object>>(getResult);
Assert.AreEqual(HttpStatusCode.Success, posRes.StatusCode);
Assert.AreEqual(testController._data, posRes.Content);

var idResult = testController.Get(1);
var oddRes = getResult as OkNegotiatedContentResult<object>; // oddRes is null
Assert.IsType<OkNegotiatedContentResult<object>>(idResult); // throws failed assertion
Assert.AreEqual(HttpStatusCode.Success, oddRes.StatusCode); // throws for null ref
Assert.AreEqual(testController._data, oddRes.Content); // throws for null ref

从黑盒之外,响应流本质上是相同的。测试必须知道控制器是如何实现返回调用的,才能以这种方式对其进行测试。

改为使用返回的 IHttpActionResult 中的 HttpResponseMessage 对象。这确保了测试可以保持一致,即使控制器代码可能不一致:

var testController = new MixedCodeStandardController();

var getResult = testController.Get();
var getResponse = getResult.ExecuteAsync(CancellationToken.None).Result;
Assert.IsTrue(getResponse.IsSuccessStatusCode);
Assert.AreEqual(HttpStatusCode.Success, getResponse.StatusCode);

var idResult = testController.Get(1);
var idResponse = idResult.ExecuteAsync(CancellationToken.None).Result;
Assert.IsTrue(idResponse.IsSuccessStatusCode);
Assert.AreEqual(HttpStatusCode.Success, idResponse.StatusCode);

【讨论】:

  • 我必须做的一件事是让这样的事情正常工作(使用 IHttpActionResult.ExecuteAsync 方法)是将 ApiController.Request 属性设置为以下内容:new HttpRequestMessage() {Properties = { { HttpPropertyKeys.HttpConfigurationKey, new HttpConfiguration() } }}
【解决方案3】:

这是 Kiran Challa 接受的答案,适用于 NUnit;

var valuesController = controller;
// if your action returns: NotFound()
IHttpActionResult actionResult = valuesController.Get(10);
var notFoundRes = actionResult as NotFoundResult;
Assert.IsNotNull(notFoundRes);

// if your action returns: Ok()
actionResult = valuesController.Get(11);
var posRes = actionResult as OkResult;
Assert.IsNotNull(posRes);

// if your action was returning data in the body like: Ok<string>("data: 12")
actionResult = valuesController.Get(12);
var conNegResult = actionResult as OkNegotiatedContentResult<string>;
Assert.IsNotNull(conNegResult);
Assert.AreEqual("data: 12", conNegResult.Content);

// if your action was returning data in the body like: Content<string>(HttpStatusCode.Accepted, "some updated data");
actionResult = valuesController.Get(13);
var negResult = actionResult as NegotiatedContentResult<string>;
Assert.IsNotNull(negResult);
Assert.AreEqual(HttpStatusCode.Accepted, negResult.StatusCode);
Assert.AreEqual("some updated data", negResult.Content);

【讨论】:

    【解决方案4】:
    【解决方案5】:

    经过几个小时的研究和尝试,我终于弄清楚了如何全面测试返回 IHttpActionResult 并使用 OWIN 中间件和 ASP.NET Identity 的默认实现的 Web API 2 方法。

    我将在以下ApiController 上测试Get() 方法:

    public class AccountController : ApiController
    {
        private ApplicationUserManager _userManager;
        public ApplicationUserManager UserManager => _userManager ?? HttpContext.Current.GetOwinContext().GetUserManager<ApplicationUserManager>();
    
        [Route("api/account"), HttpGet]
        public async Task<IHttpActionResult> Get()
        {
            var user = await UserManager.FindByIdAsync(User.Identity.GetUserId());
            if (user == null)
            {
                ModelState.AddModelError(ModelStateConstants.Errors, "Account not found! Try logging out and in again.");
                return BadRequest(ModelState);
            }
    
            var roles = await UserManager.GetRolesAsync(user.Id);
    
            var accountModel = new AccountViewModel
            {
                FullName = user.FullName,
                Email = user.Email,
                Phone = user.PhoneNumber,
                Organization = user.Organization.Name,
                Role = string.Join(", ", roles)
            };
    
            return Ok(accountModel);
        }
    
        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (_userManager != null)
                {
                    _userManager.Dispose();
                    _userManager = null;
                }
            }
    
            base.Dispose(disposing);
        }
    }
    

    从所有测试类都将继承自的基类开始:

    public class BaseTest
    {
        protected static User CurrentUser;
        protected static IList<string> Roles;
    
        public BaseTest()
        {
            var email = "unit@test.com";
    
            CurrentUser = new User
            {
                FullName = "Unit Tester",
                Email = email,
                UserName = email,
                PhoneNumber = "123456",
                Organization = new Organization
                {
                    Name = "Test Organization"
                }
            };
    
            Roles = new List<string>
            {
                "Administrator"
            };
        }
    
        protected void InitializeApiController(ApiController apiController)
        {
            //Init fake controller Http and Identity data
            var config = new HttpConfiguration();
            var request = new HttpRequestMessage();
            var routeData = new HttpRouteData(new HttpRoute(""));
            apiController.ControllerContext = new HttpControllerContext(config, routeData, request)
            {
                Configuration = config
            };
    
            apiController.User = new GenericPrincipal(new GenericIdentity(""), new[] { "" });
    
            //Initialize Mocks
            var appUserMgrMock = GetMockedApplicationUserManager();
            var appSignInMgr = GetMockedApplicationSignInManager(appUserMgrMock);
            var appDbContext = GetMockedApplicationDbContext();
    
            //Configure HttpContext.Current.GetOwinContext to return mocks
            var owin = new OwinContext();
            owin.Set(appUserMgrMock.Object);
            owin.Set(appSignInMgr.Object);
            owin.Set(appDbContext.Object);
    
            HttpContext.Current = new HttpContext(new HttpRequest(null, "http://test.com", null), new HttpResponse(null));
            HttpContext.Current.Items["owin.Environment"] = owin.Environment;
        }
    
        private static Mock<ApplicationSignInManager> GetMockedApplicationSignInManager(Mock<ApplicationUserManager> appUserMgrMock)
        {
            var authMgr = new Mock<Microsoft.Owin.Security.IAuthenticationManager>();
            var appSignInMgr = new Mock<ApplicationSignInManager>(appUserMgrMock.Object, authMgr.Object);
    
            return appSignInMgr;
        }
    
        private Mock<ApplicationUserManager> GetMockedApplicationUserManager()
        {
            var userStore = new Mock<IUserStore<User>>();
            var appUserMgr = new Mock<ApplicationUserManager>(userStore.Object);
            appUserMgr.Setup(aum => aum.FindByIdAsync(It.IsAny<string>())).ReturnsAsync(CurrentUser);
            appUserMgr.Setup(aum => aum.GetRolesAsync(It.IsAny<string>())).ReturnsAsync(Roles);
    
            return appUserMgr;
        }
    
        private static Mock<ApplicationDbContext> GetMockedApplicationDbContext()
        {
            var dbContext = new Mock<ApplicationDbContext>();
            dbContext.Setup(dbc => dbc.Users).Returns(MockedUsersDbSet);
    
            return dbContext;
        }
    
        private static IDbSet<User> MockedUsersDbSet()
        {
            var users = new List<User>
            {
                CurrentUser,
                new User
                {
                    FullName = "Testguy #1",
                    Email = "test@guy1.com",
                    UserName = "test@guy1.com",
                    PhoneNumber = "123456",
                    Organization = new Organization
                    {
                        Name = "Test Organization"
                    }
                }
            }.AsQueryable();
    
            var usersMock = new Mock<DbSet<User>>();
            usersMock.As<IQueryable<User>>().Setup(m => m.Provider).Returns(users.Provider);
            usersMock.As<IQueryable<User>>().Setup(m => m.Expression).Returns(users.Expression);
            usersMock.As<IQueryable<User>>().Setup(m => m.ElementType).Returns(users.ElementType);
            usersMock.As<IQueryable<User>>().Setup(m => m.GetEnumerator()).Returns(users.GetEnumerator);
    
            return usersMock.Object;
        }
    }
    

    InitializeApiController 方法包含肉和土豆。

    现在我们可以为AccountController编写测试:

    public class AccountControllerTests : BaseTest
    {
        private readonly AccountController _accountController;
    
        public AccountControllerTests()
        {
            _accountController = new AccountController();
            InitializeApiController(_accountController);
        }
    
        [Test]
        public async Task GetShouldReturnOk()
        {
            var result = await _accountController.Get();
            var response = await result.ExecuteAsync(CancellationToken.None);
            Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
        }
    }
    

    为了让一切正常工作,您需要安装一堆 Microsoft.OWIN.*Microsoft.AspNet.* 软件包,我将在此处粘贴我的 packages.config

    <?xml version="1.0" encoding="utf-8"?>
    <packages>
      <package id="Castle.Core" version="4.3.1" targetFramework="net472" />
      <package id="EntityFramework" version="6.2.0" targetFramework="net472" />
      <package id="Microsoft.AspNet.Identity.Core" version="2.2.2" targetFramework="net472" />
      <package id="Microsoft.AspNet.Identity.EntityFramework" version="2.2.2" targetFramework="net472" />
      <package id="Microsoft.AspNet.Identity.Owin" version="2.2.2" targetFramework="net472" />
      <package id="Microsoft.AspNet.WebApi.Client" version="5.2.7" targetFramework="net472" />
      <package id="Microsoft.AspNet.WebApi.Core" version="5.2.7" targetFramework="net472" />
      <package id="Microsoft.AspNet.WebApi.Owin" version="5.2.7" targetFramework="net472" />
      <package id="Microsoft.Owin" version="4.0.1" targetFramework="net472" />
      <package id="Microsoft.Owin.Host.SystemWeb" version="4.0.1" targetFramework="net472" />
      <package id="Microsoft.Owin.Security" version="4.0.1" targetFramework="net472" />
      <package id="Microsoft.Owin.Security.Cookies" version="4.0.1" targetFramework="net472" />
      <package id="Microsoft.Owin.Security.OAuth" version="4.0.1" targetFramework="net472" />
      <package id="Moq" version="4.10.1" targetFramework="net472" />
      <package id="Newtonsoft.Json" version="12.0.1" targetFramework="net472" />
      <package id="NUnit" version="3.11.0" targetFramework="net472" />
      <package id="Owin" version="1.0" targetFramework="net472" />
      <package id="System.Runtime.CompilerServices.Unsafe" version="4.5.2" targetFramework="net472" />
      <package id="System.Threading.Tasks.Extensions" version="4.5.2" targetFramework="net472" />
    </packages>
    

    测试非常简单,但证明一切正常:-)

    测试愉快!

    【讨论】:

      【解决方案6】:

      如果 IHttpActionResult 包含 JSON 对象,例如{"token":"A"},我们可以使用如下代码。

              var result = usercontroller.GetLogin("user", "password");
              Assert.IsInstanceOfType(result, typeof(OkNegotiatedContentResult<Dictionary<string,string>>));
              var content = result as OkNegotiatedContentResult<Dictionary<string, string> >;
              Assert.AreEqual("A", content.Content["token"]);
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-05-10
        • 1970-01-01
        • 2016-10-18
        • 2011-06-26
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多