ASP.Net MVC: unit test code when working with Vali

2019-09-15 04:26发布

问题:

I am bit familiar with ASP.Net MVC but weak in writing unit tests. I read an article about how to write unit test code when working with these classes and interface ValidationAttribute and IClientValidatable. Here is the link to the article: http://www.codeproject.com/Articles/990538/Unit-Testing-Data-Validation-with-MVC

I was doing custom validation. The custom validation code as follows

public class DateValTest
{
    [Display(Name = "Start Date")]
    [DataType(DataType.Date), DisplayFormat(DataFormatString = "{0:MM/dd/yyyy}", ApplyFormatInEditMode = true)]
    public DateTime? StartDate { get; set; }

    [Display(Name = "End Date")]
    [DataType(DataType.Date), DisplayFormat(DataFormatString = "{0:MM/dd/yyyy}", ApplyFormatInEditMode = true)]
    [MyDate(ErrorMessage = "Back date entry not allowed")]
    [DateGreaterThanAttribute(otherPropertyName = "StartDate", ErrorMessage = "End date must be greater than start date")]
    public DateTime?  EndDate { get; set; }
}

public class MyDateAttribute : ValidationAttribute, IClientValidatable
{
    public DateTime _MinDate;

    public MyDateAttribute()
    {
        _MinDate = DateTime.Today;
    }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        DateTime _EndDat = DateTime.Parse(value.ToString(), CultureInfo.InvariantCulture);
        DateTime _CurDate = DateTime.Today;

        int cmp = _EndDat.CompareTo(_CurDate);
        if (cmp > 0)
        {
            // date1 is greater means date1 is comes after date2
            return ValidationResult.Success;
        }
        else if (cmp < 0)
        {
            // date2 is greater means date1 is comes after date1
            return new ValidationResult(ErrorMessage);
        }
        else
        {
            // date1 is same as date2
            return ValidationResult.Success;
        }
    }

    public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
    {
        var rule = new ModelClientValidationRule
        {
            ErrorMessage = FormatErrorMessage(metadata.GetDisplayName()),
            ValidationType = "restrictbackdates",
        };
        rule.ValidationParameters.Add("mindate", _MinDate);
        yield return rule;
    }
}

So, I tried to write unit test code for the above validation. Please look at my unit test code and tell me if I am going in the right direction because I do not know if I wrote it properly or miss something. please have a look.

[TestClass]
public class MyDateAttribute_Test
{

    [TestMethod]
    public void IsValid_Test()
    {
        var model = new MyDateAttribute { _MinDate = DateTime.Today.AddDays(-10) }; //set value
        ExecuteValidation(model, "Back date entry not allowed");
    }

    private void ExecuteValidation(object model, string exceptionMessage)
    {
        try
        {
            var contex = new ValidationContext(model);
            Validator.ValidateObject(model, contex);
        }
        catch (ValidationException exc)
        {
            Assert.AreEqual(exceptionMessage, exc.Message);
            return;
        }
    }
}

Do I need to use Assert.AreEqual() in try block too or should it be only in catch block?

回答1:

There is no reason at all to be using a try/catch block in your TestMethod.

Firstly your attribute is implementing IClientValidatable which means you want client side validation and you can only provide one message to the ModelClientValidationRule which in turn passes it the the jQuery.Validator.

The second part of article you linked to seems to be for testing cases where you might returns different error messages based on certain conditions which cannot apply when using client side validation, and in your case, you only have one error message anyway, so all you need to test is that an invalid model will return a ValidationException.

Personally I cannot conceive of any reason to return different error messages based on different conditions in a ValidationAtribute, and the correct approach would be to create separate validation attributes. Even if you were to take that approach, all its testing is that one hard code string you have written matches another hard code string inside the attribute which is a pointless test anyway. The test would only be meaningful if you were to define properties (say) public string ErrorMessage1 { private set; get; } and public string ErrorMessage1 { private set; get; } and test that the appropriate exceptions' error message matched the values of one of those properties.

Note that you can simplify your IsValid() method to

protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
    DateTime _EndDat = DateTime.Parse(value.ToString(), CultureInfo.InvariantCulture);
    int cmp = _EndDat.CompareTo(_MinDate);
    if (cmp < 0)
    {
        return new ValidationResult(ErrorMessage);
    }
    else
    {
        return ValidationResult.Success;
    }
}

Then to test that an invalid value will throw a ValidationException

// Test that a date less than today's date is invalid
[TestMethod]
[ExpectedException(typeof(ValidationException))]
public void TestPastDateIsInvalid()
{
    DateValTest model = new DateValTest(){ EndDate = DateTime.Today.AddDays(-1) };
    ValidationContext context = new ValidationContext(model);
    MyDateAttribute attribute = new MyDateAttribute();
    attribute.Validate(model.EndDate, context);   
}


回答2:

From a logical perspective, your test is fine. That is, if Validator.ValidateObject(model, contex); successfully validates the object, then it neither returns anything (as it's a void method) nor throws an exception. So logically there is nothing to assert here.

However, depending on your unit test framework, you may need to assert something to tell the framework that your test indeed did execute and was successful (some frameworks return inconclusive if you do not have a positive assertion). So for completeness' sake I would make your method look like:

private void ExecuteValidation(object model, string exceptionMessage)
{
    try
    {
        var contex = new ValidationContext(model);
        Validator.ValidateObject(model, contex);
        Assert.IsTrue(true);
    }
    catch (ValidationException exc)
    {
        Assert.AreEqual(exceptionMessage, exc.Message);
        return;
    }
}