48 votes

Comment tester unitairement une méthode d'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 fichier List<T> de types anonymes.

Comment puis-je faire un test unitaire ?

System.Web.Mvc.JsonResult a une propriété appelée Data mais il est de type object comme nous le pensions.

Cela signifie-t-il que si je veux tester que l'objet JSON possède les propriétés que j'attends ("id", "name", "type"), je dois utiliser la réflexion ?

EDIT :

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 j'obtiens une erreur d'exécution dans la boucle, indiquant "object does not contain a definition for id".

Quand je fais un point d'arrêt, actionResult.Data est défini comme un List<T> de types anonymes, donc je me dis que si je les énumère, je peux vérifier les propriétés. Dans la boucle, l'objet fait ont une propriété appelée "id" - donc je ne suis pas sûr de la nature du problème.

0 votes

Concernant la modification, vous pouvez essayer quelque chose comme var items = (IEnumerable)actionResult.Data ; foreach(dynamic obj in items) {...}

1 votes

J'ai testé ici avec ` var list = (IList)data ; Assert.AreEqual(list.Count, 2) ; dynamic obj = data[0] ; Assert.AreEqual(obj.id, 12) ; Assert.AreEqual(obj.name, "Fred") ; Assert.AreEqual(obj.type, 'a') ; obj = data[1] ; Assert. AreEqual(obj.id,14) ; Assert.AreEqual(obj.name, "Jim") ; Assert.AreEqual(obj.type, 'c') ; foreach (dynamic d in list) { if (d.id == null) throw new InvalidOperationException() ; }` et ça semblait bien...

0 votes

Je vais essayer ce code demain quand je serai au bureau. merci.

57voto

Sergi Papaseit Points 8979

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

JsonResult renvoie un objet anonyme et ceux-ci sont, par défaut, internal Il faut donc les rendre visibles pour le projet de tests.

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

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

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

J'ai pensé que ce serait bien d'avoir celui-ci pour le dossier. Fonctionne comme un charme

19voto

Matt Greer Points 29401

RPM, vous semblez avoir raison. J'ai encore beaucoup à apprendre sur dynamic et je n'arrive pas non plus à faire fonctionner l'approche de Marc. Voici donc comment je procédais auparavant. Vous trouverez peut-être cela utile. J'ai simplement écrit 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 l'utilise simplement 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));

0 votes

Je suis content de ne pas être le seul à essayer de faire fonctionner la solution de Marc. Cela fonctionne très bien. Je vais supprimer ma réponse et l'accepter puisque c'est définitivement la meilleure approche. Merci !

1 votes

Pour mémoire, une solution dynamique fonctionnerait. Ce serait essentiellement la même chose que ceci, mais avec une syntaxe plus jolie. En interne, elle utiliserait toujours la réflexion. Un peu comme ViewBag est juste ViewData avec une syntaxe plus jolie. Je n'ai pas encore suffisamment étudié la dynamique pour l'implémenter. Je le ferai quand j'aurai un peu de temps libre. Je sais que cela implique d'implémenter IDynamicObject et en enveloppant JsonResult.Data avec elle.

1 votes

D'où vient la méthode ThrowIfNull ? Y a-t-il une classe que vous étendez ou est-ce que je rate quelque chose ?

10voto

lc. Points 50297

Je suis un peu en retard sur la fête, mais j'ai créé un petit wrapper qui me permet d'utiliser ensuite dynamic propriétés. Au moment de la rédaction de cette réponse, j'ai réussi à faire fonctionner ce système sur ASP.NET Core 1.0 RC2, mais je crois que si vous remplacez resultObject.Value con resultObject.Data cela devrait fonctionner pour les versions non essentielles.

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

2voto

Rarz Points 20

En voici un que j'utilise, peut-être est-il utile à quelqu'un d'autre. Il teste une action qui retourne un objet JSON pour une utilisation dans une fonctionnalité 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);
}

1voto

Denis Kiryanov Points 11

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

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