Bagaimana cara menguji unit metode tindakan api web ketika mengembalikan IHttpActionResult?

137

Anggaplah ini adalah metode tindakan saya

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

Tes akan

var httpActionResult = controller.Get(1);

Bagaimana cara memeriksa kode status http saya setelah ini?

sunil
sumber
4
@Fals situs yang Anda tautkan menggunakan api web 1 dan bukan merupakan tanggapan yang relevan untuk pertanyaan OP
David Peden

Jawaban:

195

Berikut Ok()ini hanyalah helper untuk tipe OkResultyang menyetel status respons menjadi HttpStatusCode.Ok... jadi Anda bisa memeriksa apakah contoh hasil tindakan Anda adalah OkResult... beberapa contoh (ditulis dalam 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
sumber
67
Di MSTestAssert.IsInstanceOfType(httpActionResult, typeof(OkResult));
sunil
2
Juga, untuk Created<T>(url,content)ituCreatedNegotiatedContentResult
sunil
1
Terima kasih Sunil..mungkin Createdbukan contoh yang baik untuk sebuah Getoperasi ... saya mengubah kode status menjadi yang berbeda sekarang ...
Kiran Challa
4
@StanimirYakimov Jenis hasil akan menjadi OkNegotiatedContentResult<T>ketika Anda mengirimkan objek jenis TkeOk()
brianestey
2
Ada bantuan dengan IHttpStatusCodes yang mengembalikan kode tidak beraturan? Seperti 422? return new StatusCodeResult((HttpStatusCode)422, this);
RoboKozo
28

Saatnya membangkitkan pertanyaan mati

Jawaban saat ini semuanya bergantung pada mentransmisikan objek respons ke tipe yang diketahui. Sayangnya, respons tampaknya tidak memiliki hierarki yang bisa digunakan atau jalur konversi implisit agar ini berfungsi tanpa pengetahuan mendalam tentang implementasi pengontrol. Pertimbangkan hal berikut:

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

Menguji kelas:

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

Dari luar kotak hitam, aliran respons pada dasarnya sama. Pengujian harus mengetahui bagaimana pengontrol mengimplementasikan panggilan balik untuk mengujinya dengan cara ini.

Sebagai gantinya, gunakan objek HttpResponseMessage dari IHttpActionResult yang dikembalikan. Ini memastikan pengujian dapat konsisten, bahkan ketika kode pengontrol mungkin tidak:

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);
psaxton
sumber
3
Satu hal yang harus saya lakukan agar sesuatu seperti ini berfungsi (menggunakan metode IHttpActionResult.ExecuteAsync) adalah menyetel atribut ApiController.Request ke yang berikut:new HttpRequestMessage() {Properties = { { HttpPropertyKeys.HttpConfigurationKey, new HttpConfiguration() } }}
tobypls
16

Ini adalah jawaban yang diterima oleh Kiran Challa, diadaptasi untuk 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);
Stanislav
sumber
3

Setelah beberapa jam meneliti dan mencoba, akhirnya saya menemukan cara untuk menguji sepenuhnya metode API Web 2 saya yang mengembalikan IHttpActionResultdan menggunakan middleware OWIN dan implementasi default dari ASP.NET Identity.

Saya akan menguji Get()metode berikut ini 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);
    }
}

Mulailah dengan kelas dasar yang akan mewarisi semua kelas pengujian:

public class BaseTest
{
    protected static User CurrentUser;
    protected static IList<string> Roles;

    public BaseTest()
    {
        var email = "[email protected]";

        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 = "[email protected]",
                UserName = "[email protected]",
                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;
    }
}

The InitializeApiControllermetode berisi daging dan kentang.

Sekarang kita bisa menulis tes kita untuk 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);
    }
}

Agar semuanya berfungsi, Anda harus menginstal banyak Microsoft.OWIN.*dan Microsoft.AspNet.*paket, saya akan menempelkannya di packages.configsini:

<?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>

Tes ini sangat sederhana, tetapi menunjukkan bahwa semuanya berfungsi :-)

Selamat menguji!

Shahin Dohan
sumber
2

Jika IHttpActionResult berisi objek JSON, misalnya {"token": "A"}, kita dapat menggunakan kode berikut.

        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"]);
Mickey
sumber