87 votes

Test unitaire des méthodes du contrôleur qui renvoient IActionResult

Je suis en train de construire une WebAPI ASP.NET Core et j'essaie d'écrire des tests unitaires pour les contrôleurs. La plupart des exemples que j'ai trouvés proviennent des anciennes plateformes WebAPI/WebAPI2 et ne semblent pas correspondre aux nouveaux contrôleurs Core.

Les méthodes de mon contrôleur renvoient IActionResults . Cependant, le IActionResult n'a qu'un ExecuteResultAsync() qui nécessite un contexte de contrôleur. Je suis en train d'instancier le contrôleur manuellement, donc le contexte du contrôleur dans cette instance est nul, ce qui provoque une exception lors de l'appel à la méthode ExecuteResultAsync . Essentiellement, cela m'amène à emprunter un chemin très compliqué pour que ces tests unitaires soient menés à bien, et c'est très désordonné. Je me demande s'il existe un moyen plus simple et plus correct de tester les contrôleurs d'API.

De plus, mes contrôleurs n'utilisent PAS async/await si cela fait une différence.

Un exemple simple de ce que j'essaie d'obtenir :

Méthode de contrôle :

[HttpGet(Name = "GetOrdersRoute")]
public IActionResult GetOrders([FromQuery]int page = 0)
{
     try
     {
        var query = _repository.GetAll().ToList();

        int totalCount = query.Count;
        int totalPages = (int)Math.Ceiling((double)totalCount / pageSize) - 1;
        var orders = query.Skip(pageSize * page).Take(pageSize);

        return Ok(new
        {
           TotalCount = totalCount,
           TotalPages = totalPages,

           Orders = orders
        });
     }
     catch (Exception ex)
     {
        return BadRequest(ex);
     }
}

Test unitaire :

[Fact]
public void GetOrders_WithOrdersInRepo_ReturnsOk()
{
     // arrange
     var controller = new OrdersController(new MockRepository());

     // act
     IActionResult result = controller.GetOrders();

     // assert
     Assert.Equal(HttpStatusCode.OK, ????);
}

1 votes

Montrer le GetOrders ce que vous retournez dans cette méthode. faites passer le résultat au type de ce que vous retournez dans la méthode et effectuez votre assert sur cela.

135voto

Nkosi Points 95895

En supposant que quelque chose comme le

public IActionResult GetOrders() {
    var orders = repository.All();
    return Ok(orders);
}

Dans ce cas, le contrôleur renvoie un OkObjectResult classe.

Transformez le résultat en type de ce que vous retournez dans la méthode et effectuez votre assert sur ce résultat.

[Fact]
public void GetOrders_WithOrdersInRepo_ReturnsOk() {
    // arrange
    var controller = new OrdersController(new MockRepository());

    // act
    var result = controller.GetOrders();
    var okResult = result as OkObjectResult;

    // assert
    Assert.IsNotNull(okResult);
    Assert.AreEqual(200, okResult.StatusCode);
}

3 votes

Rebonjour Nkosi :) Puis-je d'une manière ou d'une autre comparer l'objet résultat entier à un objet attendu, afin de pouvoir vérifier à la fois le code de retour et l'objet ? Pour l'instant, Assert.AreEqual<IActionResult>(actual, expected) ne semble pas fonctionner. Edit : L'objet est une chaîne de caractères. Dois-je faire des Assert ?

0 votes

@Squirrelkiller c'est parce que ce sont deux instances distinctes. Vous pourriez extraire l'objet du résultat et faire votre comparaison sur cela, à condition de contrôler les deux.

1 votes

@Squirrelkiller par exemple Assert.AreEquan(myObject, okResult.Value);myObject est supposé être ce qui a été retourné par votre fantaisie et passé à Ok() dans l'action du contrôleur.

22voto

Ernest Points 426

Tu peux aussi faire des trucs sympas comme :

    var result = await controller.GetOrders();//
    var okResult = result as ObjectResult;

    // assert
    Assert.NotNull(okResult);
    Assert.True(okResult is OkObjectResult);
    Assert.IsType<TheTypeYouAreExpecting>(okResult.Value);
    Assert.Equal(StatusCodes.Status200OK, okResult.StatusCode);

Merci

10voto

n1k1t0ss Points 791

D'autres réponses conseillent de faire un casting vers ObjectResult mais cela ne fonctionne que si vous retournez OkObjectResult \ NotFoundObjectResult \ etc. Mais le serveur pourrait renvoyer NotFound \ OkResult qui provient de StatusCodeResult .

Par exemple :

public class SampleController : ControllerBase
{
    public async Task<IActionResult> FooAsync(int? id)
    {
        if (id == 0)
        {
            // returned "NotFoundResult" base type "StatusCodeResult"
            return NotFound();
        }

        if (id == 1)
        {
            // returned "StatusCodeResult" base type "StatusCodeResult"
            return StatusCode(StatusCodes.Status415UnsupportedMediaType);
        }

        // returned "OkObjectResult" base type "ObjectResult"
        return new OkObjectResult("some message");
    }
}

J'ai examiné l'implémentation de toutes ces méthodes et j'ai constaté qu'elles sont toutes héritées de l'élément IStatusCodeActionResult l'interface. Il semble que ce soit le type le plus basique qui contient StatusCode :

private SampleController _sampleController = new SampleController();

[Theory]
[InlineData(0, StatusCodes.Status404NotFound)]
[InlineData(1, StatusCodes.Status415UnsupportedMediaType)]
[InlineData(2, StatusCodes.Status200OK)]
public async Task Foo_ResponseTest(int id, int expectedCode)
{
    var actionResult = await _sampleController.FooAsync(id);
    var statusCodeResult = (IStatusCodeActionResult)actionResult;
    Assert.Equal(expectedCode, statusCodeResult.StatusCode);
}

0voto

Jacek Points 21

Vous pouvez également utiliser la classe ActionResult comme résultat du contrôleur (en supposant que vous avez le type Orders). Dans ce cas, vous pouvez utiliser quelque chose comme ceci :

[ProducesResponseType(typeof(Orders), StatusCodes.Status200OK)]
public ActionResult<Orders> GetOrders()
{
    return service.GetOrders();
}

et maintenant dans les tests unitaires vous avez :

Assert.IsInstanceOf<Orders>(result.Value);

D'ailleurs, c'est la recommandation de Microsoft - https://docs.microsoft.com/en-us/aspnet/core/web-api/action-return-types?view=aspnetcore-2.2#actionresultt-type

Malheureusement, je ne sais pas pourquoi l'utilisation de la méthode Ok

return Ok(service.GetOrders());

ne le met pas en correspondance correctement.

0voto

Alex N. Points 1095

Un bon moyen de le faire est le suivant :

[Fact]
public void GetOrders_WithOrdersInRepo_ReturnsOk() {
    // arrange
    var controller = new OrdersController(new MockRepository());

    // act
    var result = controller.GetOrders();

    // assert
    var okResult = Assert.IsType<OkObjectResult>(result);
    Assert.IsNotNull(okResult);
    Assert.AreEqual(200, okResult.StatusCode);
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X