¿Cómo pruebo el método de acción de la API web cuando devuelve IHttpActionResult?

135

Asumamos que este es mi método de acción

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

La prueba será

var httpActionResult = controller.Get(1);

¿Cómo verifico mi código de estado http después de esto?

sunil
fuente
Debe echar un vistazo aquí weblogs.asp.net/shijuvarghese/archive/2013/07/30/…
Fals
44
@Fals el sitio que ha vinculado usa la API web 1 y no es una respuesta relevante a la pregunta del OP
David Peden

Respuestas:

190

Aquí Ok()hay solo una ayuda para el tipo OkResultque establece el estado de respuesta para que sea HttpStatusCode.Ok... para que pueda verificar si la instancia del resultado de su acción es un OkResult... algunos ejemplos (escritos en 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
fuente
66
En MSTestAssert.IsInstanceOfType(httpActionResult, typeof(OkResult));
sunil
2
Además, por Created<T>(url,content)suCreatedNegotiatedContentResult
sunil
1
Gracias Sunil ... probablemente Createdno fue un buen ejemplo para una Getoperación ... Cambié el código de estado a uno diferente ahora ...
Kiran Challa
44
@StanimirYakimov El tipo de resultado será OkNegotiatedContentResult<T>cuando pase un objeto de tipo TaOk()
brianestey
2
¿Alguna ayuda con IHttpStatusCodes que devuelven códigos irregulares? ¿Te gusta 422? return new StatusCodeResult((HttpStatusCode)422, this);
RoboKozo
28

Es hora de resucitar una pregunta muerta

Todas las respuestas actuales se basan en convertir el objeto de respuesta en un tipo conocido. Desafortunadamente, las respuestas no parecen tener una jerarquía utilizable o una ruta de conversión implícita para que esto funcione sin un conocimiento íntimo de la implementación del controlador. Considera lo siguiente:

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

Probar la clase:

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

Desde fuera del cuadro negro, el flujo de respuesta es esencialmente el mismo. La prueba debe saber cómo el controlador implementó la llamada de retorno para probarla de esta manera.

En su lugar, use el objeto HttpResponseMessage del IHttpActionResult devuelto. Esto asegura que la prueba puede ser consistente, incluso cuando el código del controlador no sea:

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
fuente
3
Una cosa que tuve que hacer para que algo como esto funcionara (usando el método IHttpActionResult.ExecuteAsync) fue establecer el atributo ApiController.Request en lo siguiente:new HttpRequestMessage() {Properties = { { HttpPropertyKeys.HttpConfigurationKey, new HttpConfiguration() } }}
tobypls
16

Esta es la respuesta aceptada por Kiran Challa, adaptada para 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
fuente
2

Si IHttpActionResult contiene un objeto JSON, por ejemplo, {"token": "A"}, podemos usar el siguiente código.

        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
fuente
2

Después de algunas horas de investigación e intentos, finalmente descubrí cómo probar completamente mis métodos Web API 2 que devuelven IHttpActionResulty usan el middleware OWIN y la implementación predeterminada de Identidad ASP.NET.

Probaré el Get()método en lo siguiente 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);
    }
}

Comience con una clase base que todas las clases de prueba heredarán de:

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

El InitializeApiControllermétodo contiene la carne y las papas.

Ahora podemos escribir nuestras pruebas para 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);
    }
}

Para que todo el trabajo, tendrá que instalar un montón de Microsoft.OWIN.*y Microsoft.AspNet.*paquetes, voy a pegar a mi packages.configaquí:

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

La prueba es muy simple, pero demuestra que todo funciona :-)

¡Feliz prueba!

Shahin Dohan
fuente