当它返回 IHttpActionResult 时,如何对 webapi 操作方法进行单元测试?

假设这是我的行动方法

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

测试将是

var httpActionResult = controller.Get(1);

这之后我怎么检查我的 HTTP状态码?

94193 次浏览

在这里,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);

这是 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);

是时候回答一个死掉的问题了

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

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 包含一个 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"]);

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

我将在以下 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>

这个测试非常简单,但是证明了所有的东西都是有效的: -)

测试愉快!