Sponsored

Saturday, December 26, 2020

C#/.NET: Validate Anonymous Objects During Unit Testing

Say, we have developed a REST API based on .NET Web API framework and need to write a unit test for an end point that returns a collection of objects. This sounds like a usual trivial task that many developers do all the time. Unless, the API method that we need to test, returns a collection of anonymous objects, that is the API method is written using a C# anonymous or dynamic type to construct objects that are included in the list.

It's totally fine when the endpoint is consumed over HTTP as the result is first serialized into JSON for transfer and then de-serialized back into a JavaScript object on a consumer side. If, however, the endpoint is called directly in C# without JSON conversion, like in a unit test, it's an entirely different story. Let's discuss how we can overcome this issue.

The API method method code could look like this:

var rows = dbContext.Rows.Select(row => 
    new { Name = row.NameColumn, Type = row.TypecColumn });
return Ok(rows.ToList());

When we call a method over HTTP we receive a JSON encoded array of objects with two properties in response. If we consume that response in JavaScript it is easily converted into regular JavaScript objects. If, however, we call the method in C# directly without JSON conversion like this:

// act
var result = _sut.TestMethod();

the picture we'll see is quite different. Firstly, the result is an object of type OkObjectResult, which we can, of course, validate against a null value, but we are really interested in the value it wraps:

// assert
Assert.NotNull(result);
Assert.IsType<OkObjectResult>(result);
var value = ((OkObjectResult)result).Value;
Assert.NotNull(value);

If we set a break point and investigate the value we'll see that it's an enumerable (in our case a generic List) of compiler generated anonymous type that we cannot easily validate in the code. Attempting to cast the value to something that can be validated will most likely produce a runtime error. We could try and use reflection, but why don't we actually simulate the normal calling behavior and use JSON with the help of a Newtonsoft's JSON library?

We can convert our value into a JObject or a JArray if we expect an enumerable and then examine and validate objects' properties like this:

var rows = JArray.FromObject(value);
foreach (var row in rows)
{
    Assert.NotNull(row["Name"]);
    Assert.NotNull(row["Type"]);
}

This kind of technique is easy to use and allows implementing all necessary types of assertion in the unit tests.

Best practice

Using the technique described above it is possible to implement unit testing that works against anonymous objects but I would not call this the best practice. If it was possible I would definitely opt for redesigning the original method so it returns a strongly typed output that is 100% testable without any special efforts.

No comments:

Post a Comment