ASP.Net MVC:使用ValidationAttribute和IClientValidatable时的单元测试代码

时间:2016-03-18 12:18:10

标签: c# unit-testing asp.net-mvc-5

我对ASP.Net MVC有点熟悉,但写单元测试很弱。我在阅读一篇关于如何在处理这些类和接口ValidationAttributeIClientValidatable时编写单元测试代码的文章。这是文章的链接: http://www.codeproject.com/Articles/990538/Unit-Testing-Data-Validation-with-MVC

我正在进行自定义验证。自定义验证代码如下

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

所以,我尝试为上述验证编写单元测试代码。请查看我的单元测试代码并告诉我是否正在朝着正确的方向前进,因为我不知道我是否正确编写或错过了什么。请看看。

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

我是否需要在try块中使用Assert.AreEqual(),还是仅在catch块中使用?

2 个答案:

答案 0 :(得分:1)

从逻辑角度来看,您的测试很好。也就是说,如果Validator.ValidateObject(model, contex);成功验证了对象,那么它既不返回任何内容(因为它是void方法)也不会抛出异常。所以逻辑上没有什么可以断言的。

但是,根据您的单元测试框架,您可能需要声明 来告诉框架您的测试确实执行并且成功(如果您没有正面,某些框架会返回结果)断言)。所以为了完整性&#39;我会让你的方法看起来像:

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

答案 1 :(得分:1)

完全没有理由在<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"> </script> <div style="float:right; margin-top:25px; font-size:11px;"> <input type="checkbox" checked="checked" id="include-partner-locations"> <label for="include-partner-locations">Include partner locations</label> </div> <p style="clear:both;">* Partner location</p> <div id="address-directory"> <section> <h4>Arizona</h4> <ul class="clearfix"> <li class="location-name">Phoenix <br>817 East Indian School Road <br>Phoenix, AZ&nbsp;&nbsp;85014 <br> <a class="map" href="http://maps.google.com/maps?q=817+East+Indian+School+Road+Phoenix+85014+United%20States">map</a> </li> <li class="location-name">Tempe <br>1775 W. University Drive <br>Tempe, AZ&nbsp;&nbsp;85281 <br> <a class="map" href="http://maps.google.com/maps?q=1775+W.+University+Drive+Tempe+85281+United%20States">map</a> </li> <li class="location-name partner">Tucson <br>234 East 6th Street <br>Tucson, AZ&nbsp;&nbsp;85705 <br> <a class="map" href="http://maps.google.com/maps?q=234+East+6th+Street+Tucson+85705+United%20States">map</a> </li> <li class="location-name">Tucson <br>2903 E. Broadway Blvd. <br>Tucson, AZ&nbsp;&nbsp;85716 <br> <a class="map" href="http://maps.google.com/maps?q=2903+E.+Broadway+Blvd.+Tucson+85716+United%20States">map</a> </li> </ul> </section> <section> <h4>Florida</h4> <ul class="clearfix"> <li class="location-name partner">Coral Gables <br>275 University Drive <br>Coral Gables, FL&nbsp;&nbsp;33134 <br> <a class="map" href="http://maps.google.com/maps?q=275+University+Drive+Coral+Gables+33134+United%20States">map</a> </li> <li class="location-name partner">Fort Lauderdale <br>801 N. Andrews Ave. <br>Fort Lauderdale, FL&nbsp;&nbsp;33311 <br> <a class="map" href="http://maps.google.com/maps?q=801+N.+Andrews+Ave.+Fort+Lauderdale+33311+United%20States">map</a> </li> <li class="location-name partner">Maitland <br>160 Candace Drive <br>Maitland, FL&nbsp;&nbsp;32751 <br> <a class="map" href="http://maps.google.com/maps?q=160+Candace+Drive+Maitland+32751+United%20States">map</a> </li> <li class="location-name partner">Miami <br>7451 SW 50th Terrace <br>Miami, FL&nbsp;&nbsp;33155 <br> <a class="map" href="http://maps.google.com/maps?q=7451+SW+50th+Terrace+Miami+33155+United%20States">map</a> </li> </ul> </section> <section> <h4>Arkansas</h4> <ul class="clearfix"> <li class="location-name">Bentonville <br>3204 Moberly Lane <br>Bentonville, AR&nbsp;&nbsp;72712 <br> <a class="map" href="http://maps.google.com/maps?q=3204+Moberly+Lane+Bentonville+72712+United%20States">map</a> </li> <li class="location-name">Conway <br>1101 Museum Road <br>Conway, AR&nbsp;&nbsp;72032 <br> <a class="map" href="http://maps.google.com/maps?q=1101+Museum+Road+Conway+72032+United%20States">map</a> </li> <li class="location-name">Fayetteville <br>20 Township <br>Fayetteville, AR&nbsp;&nbsp;72703 <br> <a class="map" href="http://maps.google.com/maps?q=20+Township+Fayetteville+72703+United%20States">map</a> </li> <li class="location-name">Ft. Smith <br>906 Towson Avenue <br>Ft. Smith, AR&nbsp;&nbsp;72901 <br> <a class="map" href="http://maps.google.com/maps?q=906+Towson+Avenue+Ft.+Smith+72901+United%20States">map</a> </li> <li class="location-name">Little Rock <br>815 Main <br>Little Rock, AR&nbsp;&nbsp;72201 <br> <a class="map" href="http://maps.google.com/maps?q=815+Main+Little+Rock+72201+United%20States">map</a> </li> </ul> </section> </div>中使用try/catch块。

首先,您的属性正在实施TestMethod,这意味着您需要进行客户端验证,并且您只能向IClientValidatable提供一条消息,而ModelClientValidationRule又将jQuery.Validator传递给它。

您链接到的文章的第二部分似乎是针对测试案例,您可能会根据使用客户端验证时无法应用的某些条件返回不同的错误消息,在您的情况下,您只有一条错误消息,因此,您需要测试的是,无效模型将返回ValidationException

我个人认为没有理由根据ValidationAtribute中的不同条件返回不同的错误消息,正确的方法是创建单独的验证属性。即使您采用这种方法,其所有测试都是您编写的一个硬代码字符串与属性中的另一个硬代码字符串相匹配,无论如何都是无意义的测试。只有在定义属性(例如)public string ErrorMessage1 { private set; get; }public string ErrorMessage1 { private set; get; }并测试相应的异常错误消息与其中一个属性的值匹配时,测试才有意义。

请注意,您可以将IsValid()方法简化为

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

然后测试无效值是否会抛出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);   
}