web-dev-qa-db-fra.com

Comment tester à l'unité une méthode Action qui renvoie JsonResult?

Si j'ai un contrôleur comme celui-ci:

[HttpPost]
public JsonResult FindStuff(string query) 
{
   var results = _repo.GetStuff(query);
   var jsonResult = results.Select(x => new
   {
      id = x.Id,
      name = x.Foo,
      type = x.Bar
   }).ToList();

   return Json(jsonResult);
}

En gros, je récupère des éléments de mon référentiel, puis je les projette dans un List<T> de types anonymes.

Comment puis-je le tester à l'unité?

System.Web.Mvc.JsonResult a une propriété appelée Data, mais elle est de type object, comme prévu.

Cela signifie-t-il que si je veux tester que l'objet JSON a les propriétés que je m'attends ("id", "nom", "type"), je dois utiliser la réflexion?

MODIFIER:

Voici mon test:

// Arrange.
const string autoCompleteQuery = "soho";

// Act.
var actionResult = _controller.FindLocations(autoCompleteQuery);

// Assert.
Assert.IsNotNull(actionResult, "No ActionResult returned from action method.");
dynamic jsonCollection = actionResult.Data;
foreach (dynamic json in jsonCollection)
{
   Assert.IsNotNull(json.id, 
       "JSON record does not contain \"id\" required property.");
   Assert.IsNotNull(json.name, 
       "JSON record does not contain \"name\" required property.");
   Assert.IsNotNull(json.type, 
       "JSON record does not contain \"type\" required property.");
}

Mais je reçois une erreur d’exécution dans la boucle en déclarant que "l’objet ne contient pas de définition pour id". 

Lorsque je marque un point d'arrêt, actionResult.Data est défini comme un List<T> de types anonymes. Par conséquent, si je les énumère, je peux vérifier les propriétés. À l'intérieur de la boucle, l'objet a a une propriété appelée "id" - donc vous ne savez pas quel est le problème.

40
RPM1984

RPM, vous semblez avoir raison. Il me reste encore beaucoup à apprendre sur dynamic et je ne parviens pas non plus à comprendre l'approche de Marc. Alors voici comment je le faisais avant. Vous pouvez le trouver utile. Je viens d'écrire une méthode d'extension simple:

    public static object GetReflectedProperty(this object obj, string propertyName)
    {  
        obj.ThrowIfNull("obj");
        propertyName.ThrowIfNull("propertyName");

        PropertyInfo property = obj.GetType().GetProperty(propertyName);

        if (property == null)
        {
            return null;
        }

        return property.GetValue(obj, null);
    }

Ensuite, je ne me sers que de cela pour faire des assertions sur mes données Json:

        JsonResult result = controller.MyAction(...);
                    ...
        Assert.That(result.Data, Is.Not.Null, "There should be some data for the JsonResult");
        Assert.That(result.Data.GetReflectedProperty("page"), Is.EqualTo(page));
16
Matt Greer

Je sais que je suis un peu en retard, mais j'ai découvert pourquoi la solution dynamique ne fonctionnait pas:

JsonResult renvoie un objet anonyme. Par défaut, internal, ils doivent donc être rendus visibles par le projet de test.

Ouvrez votre projet d'application ASP.NET MVC et recherchez AssemblyInfo.cs dans le dossier appelé Propriétés. Ouvrez AssemblyInfo.cs et ajoutez la ligne suivante à la fin de ce fichier.

[Assembly: InternalsVisibleTo("MyProject.Tests")]

Cité de:http://weblogs.asp.net/gunnarpeipman/archive/2010/07/24/asp-net-mvc-using-dynamic-type-to-test-controller-actions-returning -jsonresult.aspx

Je pensais que ce serait bien d'avoir celui-ci pour l'enregistrement. Fonctionne comme un charme

51
Sergi Papaseit

Je suis un peu en retard pour la fête, mais j'ai créé un petit wrapper qui me permet ensuite d'utiliser les propriétés dynamic. En ce qui concerne cette réponse, cela fonctionne sur ASP.NET Core 1.0 RC2, mais je crois que si vous remplacez resultObject.Value par resultObject.Data, cela devrait fonctionner pour les versions non principales.

public class JsonResultDynamicWrapper : DynamicObject
{
    private readonly object _resultObject;

    public JsonResultDynamicWrapper([NotNull] JsonResult resultObject)
    {
        if (resultObject == null) throw new ArgumentNullException(nameof(resultObject));
        _resultObject = resultObject.Value;
    }

    public override bool TryGetMember(GetMemberBinder binder, out object result)
    {
        if (string.IsNullOrEmpty(binder.Name))
        {
            result = null;
            return false;
        }

        PropertyInfo property = _resultObject.GetType().GetProperty(binder.Name);

        if (property == null)
        {
            result = null;
            return false;
        }

        result = property.GetValue(_resultObject, null);
        return true;
    }
}

Utilisation, en supposant que le contrôleur suivant:

public class FooController : Controller
{
    public IActionResult Get()
    {
        return Json(new {Bar = "Bar", Baz = "Baz"});
    }
}

Le test (xUnit):

// Arrange
var controller = new FoosController();

// Act
var result = await controller.Get();

// Assert
var resultObject = Assert.IsType<JsonResult>(result);
dynamic resultData = new JsonResultDynamicWrapper(resultObject);
Assert.Equal("Bar", resultData.Bar);
Assert.Equal("Baz", resultData.Baz);
6
lc.

Ma solution est d'écrire la méthode d'extension:

using System.Reflection;
using System.Web.Mvc;

namespace Tests.Extensions
{
    public static class JsonExtensions
    {
        public static object GetPropertyValue(this JsonResult json, string propertyName)
        {
            return json.Data.GetType().GetProperty(propertyName, BindingFlags.Instance | BindingFlags.Public).GetValue(json.Data, null);
        }
    }
}
1
Denis Kiryanov

En voici un que j'utilise, il est peut-être utile à quiconque. Il teste une action qui renvoie un objet JSON à utiliser dans les fonctionnalités côté client. Il utilise Moq et FluentAssertions.

[TestMethod]
public void GetActivationcode_Should_Return_JSON_With_Filled_Model()
{
    // Arrange...
    ActivatiecodeController activatiecodeController = this.ActivatiecodeControllerFactory();
    CodeModel model = new CodeModel { Activation = "XYZZY", Lifespan = 10000 };
    this.deviceActivatieModelBuilder.Setup(x => x.GenereerNieuweActivatiecode()).Returns(model);

    // Act...
    var result = activatiecodeController.GetActivationcode() as JsonResult;

    // Assert...
    ((CodeModel)result.Data).Activation.Should().Be("XYZZY");
    ((CodeModel)result.Data).Lifespan.Should().Be(10000);
}
1
MartijnK

J'étends la solution de Matt Greer et viens avec cette petite extension:

    public static JsonResult IsJson(this ActionResult result)
    {
        Assert.IsInstanceOf<JsonResult>(result);
        return (JsonResult) result;
    }

    public static JsonResult WithModel(this JsonResult result, object model)
    {
        var props = model.GetType().GetProperties();
        foreach (var prop in props)
        {
            var mv = model.GetReflectedProperty(prop.Name);
            var expected = result.Data.GetReflectedProperty(prop.Name);
            Assert.AreEqual(expected, mv);
        }
        return result;
    }

Et je viens de lancer le unittest comme ceci: - Définir le résultat de données attendu:

        var expected = new
        {
            Success = false,
            Message = "Name is required"
        };

- Affirmer le résultat:

        // Assert
        result.IsJson().WithModel(expected);
1
Thai Anh Duc

Si dans le test, vous savez exactement ce que le résultat de données Json devrait être, vous pouvez simplement faire quelque chose comme ceci:

result.Data.ToString().Should().Be(new { param = value}.ToString());

P.S. Ce serait le cas si vous aviez utilisé FluentAssertions.Mvc5 - mais il ne devrait pas être difficile de le convertir en outils de test que vous utilisiez.

0
AssassinLV