このようなコントローラーがある場合:
[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);
}
基本的には、リポジトリからデータを取得し、List<T>
の匿名型に投影します。
単体テストを行うにはどうすればよいですか?
System.Web.Mvc.JsonResult
にはData
というプロパティがありますが、予想どおりobject
型です。
だから、JSONオブジェクトが期待するプロパティ(「id」、「name」、「type」)を持っていることをテストしたい場合、リフレクションを使用する必要がありますか?
編集:
私のテストは次のとおりです。
// 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.");
}
しかし、ループには「オブジェクトにidの定義が含まれていません」という実行時エラーが発生します。
ブレークポイントを設定すると、actionResult.Data
は匿名型のList<T>
として定義されるため、これらを列挙すると、プロパティを確認できます。ループ内では、オブジェクトdoesには「id」というプロパティがあります。そのため、問題が何であるかはわかりません。
RPM、あなたは正しいように見えます。私はまだdynamic
について学ぶべきことがたくさんあり、Marcのアプローチも機能しません。だからここに私が前にそれをやっていた方法があります。役に立つかもしれません。私は単純な拡張メソッドを書きました:
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);
}
次に、それを使用して、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));
私はこの人たちに少し遅れていることを知っていますが、動的なソリューションが機能していなかった理由を見つけました:
JsonResult
は匿名オブジェクトを返し、これらはデフォルトでinternal
であるため、テストプロジェクトから見えるようにする必要があります。
ASP.NET MVCアプリケーションプロジェクトを開き、
AssemblyInfo.cs
プロパティというフォルダから。 AssemblyInfo.csを開き、このファイルの最後に次の行を追加します。
[Assembly: InternalsVisibleTo("MyProject.Tests")]
記録のためにこれを持っていればいいと思った。魔法のように働く
私はパーティーに少し遅れましたが、dynamic
プロパティを使用できる小さなラッパーを作成しました。この回答の時点で、これはASP.NET Core 1.0 RC2で動作していますが、resultObject.Value
with resultObject.Data
コア以外のバージョンでも動作するはずです。
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;
}
}
以下のコントローラーを想定した使用法:
public class FooController : Controller
{
public IActionResult Get()
{
return Json(new {Bar = "Bar", Baz = "Baz"});
}
}
テスト(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);
私の解決策は、拡張メソッドを書くことです:
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);
}
}
}
ここに私が使用するものがあります。おそらく誰にとっても役に立つでしょう。クライアント側の機能で使用するJSONオブジェクトを返すアクションをテストします。 Moqと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);
}
Matt Greerのソリューションを拡張し、次の小さな拡張機能を思い付きます。
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;
}
そして、私はこれとしてunittestを実行します:-予想されるデータ結果を設定します:
var expected = new
{
Success = false,
Message = "Name is required"
};
-結果をアサートします。
// Assert
result.IsJson().WithModel(expected);
テストで、Jsonデータの結果がどうあるべきかを正確に知っている場合、次のようなことができます。
result.Data.ToString().Should().Be(new { param = value}.ToString());
追伸これは、FluentAssertions.Mvc5を使用した場合です。ただし、使用するテストツールに変換するのは難しくありません。
これは私がそれを主張する方法です
foreach (var item in jsonResult.Data as dynamic) {
((int)item.Id).ShouldBe( expected Id value );
((string)item.name).ShouldBe( "expected name value" );
}