Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C# Unit testing a Newtonsoft JSON model

I am starting with unit testing and am doing so with XUnit.

I have a model :

using Newtonsoft.Json;
namespace HomeAddressSearch
{
    public class Properties
    {
        [Required]
        [JsonProperty(PropertyName = "civic_number")]
        public string CivicNumber { get; set; }

        [JsonProperty(PropertyName = "address")]
        public string Address { get; set; }

        [JsonProperty(PropertyName = "postal_code")]
        public string PostalCode { get; set; }

        [JsonProperty(PropertyName = "city_name")]
        public string CityName { get; set; }
    }
}

and I have a JSON from which I am getting values like :

[
  {
    "properties": {
      "civic_number": "100",
      "address": "100, king street",
      "postal_code": "H0H0H0",
      "city_name": "Windsor"
    },
    "owner": {
      "name": "John Doe",
      "age": "40"
    },
  }
]

So I want to test that model and for so I though of:

  • As CivicNumber is Required I want to make sure there is no Null value or empty string in the JSON, so a test with a null value crashing the test
  • If ever someone would change the JSON and remove one of the field in the properties object, like city_name, then the test would fail so I know I either need to adapt my model or get back to the original JSON format
  • Check that format of JSON values, in this case all of them are string, are ok to be injected in my model
  • Run an injection from a shorter version of a JSON file with like only 3 entries into the model to make sure everything is ok

Am I on the right path and how to do this, that would greatly help and kickstart me for a lot of other things.

THanks!

like image 960
KaptainJ Avatar asked Nov 20 '25 08:11

KaptainJ


1 Answers

This wouldn't be something you need to unit test, but rather utilize Newtonsoft's JSON validation.

// Generate a JSON schema from your object.
var schemaGenerator = new JSchemaGenerator();
var schema = schemaGenerator.Generate(typeof(HomeAddressSearch.Properties)); 

// Parse the JSON passed in, then validate it against the schema before proceeding.
List<ValidationErrors> validationErrors;
JObject addressToValidate = JObject.Parse(jsonStringToValidate);
bool isValid = addressToValidate.IsValid(schema, out validationErrors);

From there you can iterate through the validation errors if you want to elaborate for a response to the caller.

Unit tests are suited to asserting static code behaviour so if your JSON schema specifies rules, the assertion that a unit test does is:

  • Assert that your code validated the JSON Schema and rejected it if invalid.
  • Any behaviour your code should do with valid JSON data scenarios.

** Edit ** To elaborate on using the validation, then unit testing that the validation is performed:

Lets say we have an API that accepts a JSON data as a string rather than as a class: (where we should be able to use model.IsValid())

public class AddressController : ApiController
{
  private readonly IJsonEntityValidator<HomeAddressSearch.Properties> _validator = null;

  public AddressController(IJsonEntityValidator validator)
  {
    _validator = validator;
  }
  public ActionResult Search(string jsonModel)
  { 
    if(string.IsNullOrEmpty(jsonModel))
      // handle response for missing model.

    var result = _validator.Validate<HomeAddressSearch.Properties>(jsonModel);
    if (!result.IsValid) 
      // handle validation failure for the model.
  }
}

public class JSonEntityValidator<T> : IJsonEntityValidator<T> where T: class
{
  IJsonEntityValidator<T>.Validate(string jsonModel)
  {
    // Generate a JSON schema from your object.
    var schemaGenerator = new JSchemaGenerator();
    var schema = schemaGenerator.Generate(typeof(T)); 

    // Parse the JSON passed in, then validate it against the schema before proceeding.
    List<ValidationErrors> validationErrors;
    JObject model = JObject.Parse(jsonModel);
    bool isValid = model.IsValid(schema, out validationErrors);
    return new JsonValidateResult
    {
      IsValid = isValid,
      ValidationErrors = validationErrors
    };
  }
}

The unit test would be against the Controller, to assert that it used the validator to ensure the JSON data was valid:

[Test]
public void EnsureHomeAddressSearchValidatesProvidedJson_InvalidJSONScenario()
{
  string testJson = buildInvalidJson(); // the value here doesn't matter beyond being recognized by our mock as being passed to the validation.
  var mockValidator = new Mock<IJsonEntityValidator<HomeAddressSearch.Properties>>();
  mockValidator.Setup(x => x.Validate(testJson)
    .Returns(new JsonValidationResult { IsValid = false, ValidationErrors = new ValidationError[] { /* populate a set of validation errors. */ }.ToList()});
  var testController = new AddressController(mockValidator.Object);
  var result = testController.Search(testJson);
  // Can assess the result from the controller based on the scenario validation errors returned....

  // Validate that our controller called the validator.
  mockValidator.Verify(x => x.Validate(testJson), Times.Once);
}

What this test accomplishes is asserting that our controller will call the validation logic to assess our provided JSON. If someone modifies the controller and removes the call to Validate, the test will fail.

You don't need to compose a JSON object for a test like this, "fred" will be fine as it's just a placeholder for the mock to recognize. The mock in this scenario is configured that: "I expect to be called with a specific value. [fred]" "When I get that value, I'm going to say it isn't valid, and I'm going to include a specific set of validation errors." From there you can assert the result response from the Controller to see if it reflects the validation errors. We also ask the Mock at the end to verify that it was called with the specific value.

Now if you also want to enforce/assert against changes to your schema then you can write tests against your validator itself. Such as to detect someone removing or changing an attribute on your model.

[Test]
EnsureHomeAddressValidatorExpectsCivicNumberIsRequired()
{
  var testJson = generateJsonMissingCivicNumber();
  IJsonEntityValidator<HomeAddressSearch.Properties> testValidator = new JsonEntityValidator<HomeAddressSearch.Properties>();
  var result = testValidator.Validate(testJson);
  Assert.IsFalse(result.IsValid);
  // Assert result.ValidationErrors....
}
like image 115
Steve Py Avatar answered Nov 21 '25 22:11

Steve Py



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!