如何在返回IHttpActionResult时对web api动作方法进行单元测试?

时间:2013-11-12 18:17:49

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

我们假设这是我的行动方法

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

测试将

var httpActionResult = controller.Get(1);

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

6 个答案:

答案 0 :(得分:181)

此处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);

答案 1 :(得分:25)

恢复死亡问题的时间

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

var captureSession: AVCaptureSession!
    var previewLayer: AVCaptureVideoPreviewLayer!
    let videoCaptureDevice = AVCaptureDevice.defaultDevice(withMediaType: AVMediaTypeVideo)

    captureSession = AVCaptureSession()

    let videoInput: AVCaptureDeviceInput

    do {
        videoInput = try AVCaptureDeviceInput(device: videoCaptureDevice)

        if captureSession.canAddInput(videoInput) {
            captureSession.addInput(videoInput)

            let metadataOutput = AVCaptureMetadataOutput()

            if captureSession.canAddOutput(metadataOutput) {
                captureSession.addOutput(metadataOutput)

                metadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
                metadataOutput.metadataObjectTypes = metadataOutput.availableMetadataObjectTypes // Use all metadata object types by default.
                metadataOutput.rectOfInterest = CGRect.zero

            } else {
                failed()
                return
            }

            if (videoCaptureDevice?.isFocusModeSupported(.continuousAutoFocus))! {
                do {
                    if(try videoCaptureDevice?.lockForConfiguration()) != nil {
                        videoCaptureDevice?.exposureMode = .continuousAutoExposure
                        videoCaptureDevice?.focusMode = .continuousAutoFocus
                        videoCaptureDevice?.unlockForConfiguration()
                    }
                } catch {

                }
            }

            videoCaptureDevice?.addObserver(self, forKeyPath: "adjustingFocus", options: NSKeyValueObservingOptions.new, context: nil)
            let tapGesture = UITapGestureRecognizer(target: self, action: #selector(ScannerViewController.focus(_:)))
            mainView.addGestureRecognizer(tapGesture)

            previewLayer = AVCaptureVideoPreviewLayer(session: captureSession);
            previewLayer.frame = view.layer.bounds;
            previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
            mainView.layer.addSublayer(previewLayer);

            /*
            // Initialize code Frame to highlight the code
            codeFrameView.layer.borderColor = UIColor.green.cgColor
            codeFrameView.layer.borderWidth = 2
            view.addSubview(codeFrameView)
            view.bringSubview(toFront: codeFrameView)
            */

            captureSession.startRunning()

        } else {
            failed()
        }
    } catch {
        failed()
    }

测试课程:

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);
    }
}

从黑匣子外面,响应流基本相同。测试必须知道控制器如何实现返回调用以便以这种方式测试它。

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

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

答案 2 :(得分:14)

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

答案 3 :(得分:5)

答案 4 :(得分:1)

如果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"]);

答案 5 :(得分:0)

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

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

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>

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

测试愉快!