How should I unit test multiple required fields in C#? - c#

Imagine this simple scenario: I have a class called MyModel:
public class MyModel
{
public string Prop01 { get; set; }
public string Prop02 { get; set; }
public string Prop03 { get; set; }
public bool IsValid()
{
if (String.IsNullOrEmpty(Prop01) || String.IsNullOrEmpty(Prop02) || String.IsNullOrEmpty(Prop03))
return false;
return true;
}
}
As, you can see, if any of the properties on MyModel is null or empty, the IsValid() method will return false, in other words, all the fields are "required".
I wrote some Unit Tests to test the IsValid() method:
[TestMethod]
public void MyModel_Invalid_When_Prop01_Is_Null()
{
var myModel = new MyModel();
Assert.AreEqual(myModel.IsValid(), false);
}
[TestMethod]
public void MyModel_Invalid_When_Prop02_Is_Null()
{
var myModel = new MyModel();
Assert.AreEqual(myModel.IsValid(), false);
}
[TestMethod]
public void MyModel_Invalid_When_Prop03_Is_Null()
{
var myModel = new MyModel();
Assert.AreEqual(myModel.IsValid(), false);
}
Of course all those tests will pass, but I'm not quite happy with that. Let's imagine I'm a developer that saw the MyModel_Invalid_When_Prop01_Is_Null test (that was writen by another developer). I would expect that just by assigning a value to the Prop01 of myModel, the test should start failing. But of course it won't happen, so I changed the tests to look like this:
[TestMethod]
public void MyModel_Invalid_When_Prop01_Is_Null()
{
var myModel = new MyModel();
myModel.Prop02 = "Some value";
myModel.Prop03 = "Some value";
Assert.AreEqual(myModel.IsValid(), false);
}
[TestMethod]
public void MyModel_Invalid_When_Prop02_Is_Null()
{
var myModel = new MyModel();
myModel.Prop01 = "Some value";
myModel.Prop03 = "Some value";
Assert.AreEqual(myModel.IsValid(), false);
}
[TestMethod]
public void MyModel_Invalid_When_Prop03_Is_Null()
{
var myModel = new MyModel();
myModel.Prop01 = "Some value";
myModel.Prop02 = "Some value";
Assert.AreEqual(myModel.IsValid(), false);
}
Now the tests are really testing that each of the properties are filled, but if I add a Prop04 to MyModel, that is also required field, I would need to change all my Unit Tests again, so I don't think this is a good idea.
So my question is: How can I unit test multiple required properties in a way that I'm sure the test is either passing or failing because of that specific property I'm currently testing? Or maybe, Should I be testing those scenarios?

Instead of starting with an empty, invalid model, you could start with a valid one and then make it invalid. That will make sure you only have to modify your tests in one place, which is fine, because your actual requirements changed.
An added advantage is that your tests become more explicit, because the setup is explicitly making your model invalid:
[TestMethod]
public void MyModel_Invalid_When_Prop01_Is_Null()
{
var myModel = getValidModel();
myModel.Prop01 = null;
Assert.AreEqual(myModel.IsValid(), false);
}
[TestMethod]
public void MyModel_Invalid_When_Prop02_Is_Null()
{
var myModel = getValidModel();
myModel.Prop02 = null;
Assert.AreEqual(myModel.IsValid(), false);
}
[TestMethod]
public void MyModel_Invalid_When_Prop03_Is_Null()
{
var myModel = getValidModel();
myModel.Prop03 = null;
Assert.AreEqual(myModel.IsValid(), false);
}
MyModel getValidModel() =>
new MyModel
{
Prop01 = "Some value",
Prop02 = "Some value",
Prop03 = "Some value",
};
If your model initialization becomes more complex, you could make use of the builder pattern.
I wrote a blog-post about this which may be helpful: https://www.kenneth-truyers.net/2013/07/15/flexible-and-expressive-unit-tests-with-the-builder-pattern/

In my opinion, if isValid() is important enough to be a method then it's important enough that its behavior should be tested.
In some ways the simplicity of its logic may lull you into feeling it can't be worth all the work. Well, I get it, but the counter-argument is if someone adds another required field, failing to update isValid() is a very plausible mistake; and if you're not testing the behavior if isValid() then you won't know until some mysterious production bug where nobody thinks to look for Prop04 to be Null because hey, isValid() returns true...
So yes, I'd test it; but yes, you can make that easier. You could create a single helper function in your test class that produces a dummy instance of the MyModel with all fields populated. Then your test methods just look like
(Pardon any code typos; I'm not at a compiler but I think you'll see what I mean...)
[TestMethod]
public void MyModel_Invalid_When_Prop01_Is_Null()
{
var myModel = getMyModelInstance();
myModel.Prop01 = Null;
Assert.AreEqual(myModel.IsValid(), false);
}
And of course you'd have another test method that doesn't set any of the properties to Null and asserts that IsValid() should return true.
So now you add a new property, you add a new test method (which you'd expect to have to do). You update the helper to populate the new property; you'd need that to support the "IsValid() is true" case anyway. And so none of the other existing test methods needs updating.

Related

Testing a property set to an instance of a new object in Rhino Mocks 3.4.0

Background
I'm fixing unit tests which have been neglected for a long time for legacy code in our organisation. They're written using Rhino Mocks 3.4.0, and I'm struggling to find a way of making this test pass. Rhino Mocks documentation seems to have gone, and most answers here and blogs seem to be using updated 3.5 and 3.6 syntax.
I'm wary of updating the version of Rhino Mocks we're using, as we have several thousand unit tests which may or may not need updated if we update.
The scenario:
We have a Presenter and a View. When the Presenter is initialised, it sets some default filter properties in the View. In the past, both of these properties were enums and the test passed.
The last change updated one of the properties to be an instance of a class. The test was updated to expect a call to a static method which creates an instance with default values (matching the code under test), but the test now fails with the error Rhino.Mocks.Exceptions.ExpectationViolationException : Unordered method call.
Some sample code:
public enum FilterOptions { OptionA, OptionB, OptionC }
public class OtherFilterOptions
{
public bool Filter1 { get; set;}
public bool Filter2 { get; set; }
public OtherFilterOptions(bool filter1 = true, bool filter2 = false)
{
Filter1 = filter1;
Filter2 = filter2;
}
public static OtherFilterOptions DefaultFilterOptions()
{
return new OtherFilterOptions();
}
}
public interface IToTestView
{
FilterOptions Property1 { set; }
OtherFilterOptions Property2 { set; }
}
public class ToTestPresenter
{
public IToTestView View { get; set; }
public ToTestPresenter(IToTestView view)
{
View = view;
}
public void InitialiseView()
{
View.Property1 = FilterOptions.OptionA;
View.Property2 = OtherFilterOptions.DefaultFilterOptions();
}
}
And a failing test:
[TestFixture]
class Tests
{
[Test]
public void TestOne()
{
var mocks = new MockRepository();
var mockView = mocks.CreateMock<IToTestView>();
ToTestPresenter presenter = new ToTestPresenter(mockView);
using (mocks.Ordered())
{
mockView.Property1 = FilterOptions.OptionA;
mockView.Property2 = OtherFilterOptions.DefaultFilterOptions();
}
mocks.ReplayAll();
presenter.InitialiseView();
mocks.VerifyAll();
}
}
The full error is
Rhino.Mocks.Exceptions.ExpectationViolationException : Unordered method call! The expected call is: 'Ordered: { IToTestView.set_Property2(RhinoMocksTestApp.OtherFilterOptions); }' but was: 'IToTestView.set_Property2(RhinoMocksTestApp.OtherFilterOptions);'
I'm assuming that the test is failing because the value to be set is a method call rather than a concrete value. I've tried declaring a variable using mockView.Property2 = theVariable, but there's no change to the error.
Can I set an expectation that Property2 will be set to {some object with Values Filter1 = true, Filter2 = false}? I've seen examples doing similarly using Rhino Mocks 3.6, but is anything available using 3.4.0?
Edit:
As an example, this is an example test which passes in Rhino Mocks 3.6.1 - I'm hoping to find some syntax that works similarly for 3.4.0, if it exists.
[Test]
public void TestOne()
{
var mocks = new MockRepository();
var mockView = MockRepository.GenerateMock<IToTestView>();
ToTestPresenter presenter = new ToTestPresenter(mockView);
mocks.ReplayAll();
presenter.InitialiseView();
mockView.AssertWasCalled(v => v.Property1 = FilterOptions.OptionA);
mockView.AssertWasCalled(v => v.Property2 = Arg<OtherFilterOptions>.Matches(filters =>
(filters.Filter1 == true) && (filters.Filter2 == false)));
}
The answer I was looking for was in the LastCall.Constraints() method. Passing arguments to Constraints allows you to specify property values of an argument:
[Test]
public void TestOne()
{
var mocks = new MockRepository();
var mockView = mocks.CreateMock<IToTestView>();
ToTestPresenter presenter = new ToTestPresenter(mockView);
using (mocks.Ordered())
{
mockView.Property1 = FilterOptions.OptionA;
mockView.Property2 = OtherFilterOptions.DefaultFilterOptions();
LastCall.Constraints(
Property.Value("Filter1", true)
& Property.Value("Filter2", false));
}
mocks.ReplayAll();
presenter.InitialiseView();
mocks.VerifyAll();
}
There are a large number of options that can be passed in to the Constraints() method. Details on some of them on this CodeProject page
Another option is LastCall.IgnoreArguments() if you don't care what the property is actually set to.

Unit Test for two classes inherit an Interface

I have two classes that implement an interface, but both classes have a parameter passed into the constructor to identify what class the application would need. I am trying to test one (GetAvailablity) method on (AvailablityRepoData) class when I create an instance of AvailablityRepoData I am getting an error for non-virtual method. I would really appreciate if someone can point me to the right direction.
public interface IAvailablityRepo
{
string GetAvailablity(Availablity availablity);
}
public class AvailablityRepoData: IAvailablityRepo
{
public AvailablityRepoData(string websetting) {
}
public string GetAvailablity(Availablity availablity) {
return "Data";
}
}
public class AvailablityRepoWeb:IAvailablityRepo
{
public AvailablityRepoWeb(string DataSetting) {
}
public string GetAvailablity(Availablity availablity) {
return "Web";
}
}
public class Availablity
{
public virtual string Id {
get;
set;
}
public virtual string Status {
get;
set;
}
}
var a = new Availablity() { Id = "111", Status = "A"};
Mock<IAvailablityRepo> mockRepo = new Mock<IAvailablityRepo>();
Mock<IAvailablityRepo> RepoData = new Mock<IAvailablityRepo>();
RepoData.Setup(x => x.GetAvailablity(It.IsAny<Availablity> ())).Returns("pass");
var result = RepoData.Object.GetAvailablity(a);
As has already been said in the comments, it's not clear from the code you've posted what your error is. If I copy and past it straight into visual studio (wrapping the test code in a test), the test passes fine. I'm going to suggest that when you experienced the error, you test code was actually closer to this:
[TestMethod]
public void TestMethod1() {
var a = new Availablity() { Id = "111", Status = "A" };
Mock<IAvailablityRepo> mockRepo = new Mock<IAvailablityRepo>();
Mock<AvailablityRepoData> RepoData = new Mock<AvailablityRepoData>();
RepoData.Setup(x => x.GetAvailablity(It.IsAny<Availablity>())).Returns("pass");
var result = RepoData.Object.GetAvailablity(a);
}
This results in an error when the test is run:
System.NotSupportedException:Invalid setup on a non-virtual (overridable in VB) member:
x => x.GetAvailablity(It.IsAny<Availablity>())
The difference between this test and your original test is that I've changed the Mocked type from the interface IAvailabilityRepo to AvailabilityRepoData which is the concrete class. Since Moq only supports mocking of interfaces / virtual methods it's naturally getting upset.
As has been mentioned by #prgmtc, your test as it stands doesn't really testing much of anything.
With your current code, it doesn't actually look like you need to be using Mocks at all. Something like this might be a more appropriate test:
[TestMethod]
public void TestDataRepoReturnsDataAvailability() {
var someImportantSetting = "thisShouldBeSomethingMeaningful";
var availability = new Availablity() { Id = "111", Status = "A" };
var sut = new AvailablityRepoData(someImportantSetting);
var returnedAvailability = sut.GetAvailablity(availability);
Assert.AreEqual("Data", returnedAvailability);
}
Assuming your actual code is more complex the string passed into your data repo would presumably need to be rather more meaningful...
As a general rule of thumb, you shouldn't be mocking the system under test. If you find yourself creating a mock for the system you're testing it's a good indication that you've got to much functionality in one class and/or you're trying to test the wrong thing...
As an asside, you may want to look into something like the builder pattern to create your different repos rather than passing the type into the constructor for each of the repos as you seem to be suggesting.

Methods adapted to satisfy unit tests. This just seems wrong

I have begun to dabble in unit testing, and have created a couple of tests that test one of my presenter methods. This testing requires a mock of my data access class, and one method in particular. This is the original method from my data access class:
public IEnumerable<IArea> GetAreaList()
{
ConnectToTFSProject();
XmlNode areaNode = GetAreaNode();
List<IArea> areaList = new List<IArea>();
foreach (XmlNode node in areaNode.FirstChild.ChildNodes)
{
IArea area = new Area() { AreaName = node.Attributes["Name"].Value };
areaList.Add(area);
}
areaList.Sort();
return areaList;
}
I would like to test the presenter method with different scenarios, e.g.:
a regular list of areas
an empty list of areas
a list of areas with duplicates
a list or areas containing one empty string area
My first thought was to create a separate mock data access class for each of these scenarios. I thought this to be a little cumbersome, so I adapted the method slightly to allow the reading of different xml files, containing data specific to the current test. Here is how my mock method looks:
public IEnumerable<IArea> GetAreaList(string dataSource)
{
List<IArea> areaList = new List<IArea>();
XmlTextReader areaReader = new XmlTextReader(dataSource);
while (areaReader.Read())
{
if (areaReader.NodeType == XmlNodeType.Text)
areaList.Add(new Area() { AreaName = areaReader.Value });
}
return areaList;
}
This mock method will then be called from the PresenterTest class as follows:
[TestMethod]
public void PopulateAreaComboBox_WithValidAreaList()
{
//Act
_presenter.PopulateAreaComboBox(mockFolderPath + "MockAreaList.xml");
//Assert
Assert.AreEqual(3, _view.AreaListLoaded.Count);
}
[TestMethod]
public void PopulateAreaComboBox_WithEmptyAreaList()
{
//Act
_presenter.PopulateAreaComboBox(mockFolderPath + "MockEmptyAreaList.xml");
//Assert
Assert.AreEqual(0, _view.AreaListLoaded.Count);
}
Now, the problem I have here is that I now need to change the signature of my original method (by adding reference to the dataSource parameter):
public IEnumerable<IArea> GetAreaList(string dataSource)
Because this parameter is required only for the unit tests, the value of null is passed into this method from the real presenter class, and never used.
I know this is wrong but how should this be accomplished? Should I create a separate mock data access class that sets up each test data scenario?
I resolved this by exposing a public string (TestDataXml) in my mock data access class. I then created a new instance of the mock data access class for each test, setting this string to the location of an individual test data xml file each time:
class MockDataRetrieval : IDataRetrieval
{
public string TestDataXml { get; set; }
public IEnumerable<IArea> GetAreaList()
{
List<IArea> areaList = new List<IArea>();
XmlTextReader areaReader = new XmlTextReader(TestDataXml);
while (areaReader.Read())
{
if (areaReader.NodeType == XmlNodeType.Text)
areaList.Add(new Area() { AreaName = areaReader.Value });
}
return areaList;
}
}
To call the mock method from the PresenterTest class:
[TestMethod]
public void PopulateAreaComboBox_WithValidAreaList()
{
//Arrange
_data = new MockDataRetrieval() { TestDataXml = mockFolderPath + "MockAreaList.xml" };
_view = new MockMainForm();
_presenter = new TestCasePresenter(_view, _data);
//Act
_presenter.PopulateAreaComboBox();
//Assert
Assert.AreEqual(3, _view.AreaListLoaded.Count);
}
[TestMethod]
public void PopulateAreaComboBox_WithEmptyAreaList()
{
//Arrange
_data = new MockDataRetrieval() { TestDataXml = mockFolderPath + "MockEmptyAreaList.xml" };
_view = new MockMainForm();
_presenter = new TestCasePresenter(_view, _data);
//Act
_presenter.PopulateAreaComboBox();
//Assert
Assert.AreEqual(0, _view.AreaListLoaded.Count);
}
Ralf: Thanks for your comment. This question relates to testing the Presenter.PopulateAreaComboBox method. The GetAreaList method in this example is from the mock data access class, and simply provides the method under test with test data.

How do I invoke a validation attribute for testing?

I am using the RegularExpressionAttribute from DataAnnotations for validation and would like to test my regex. Is there a way to invoke the attribute directly in a unit test?
I would like to be able to do something similar to this:
public class Person
{
[RegularExpression(#"^[0-9]{3}-[0-9]{3}-[0-9]{4}$")]
public string PhoneNumber { get; set; }
}
Then in a unit test:
[TestMethod]
public void PhoneNumberIsValid
{
var dude = new Person();
dude.PhoneNumber = "555-867-5309";
Assert.IsTrue(dude.IsValid);
}
Or even
Assert.IsTrue(dude.PhoneNumber.IsValid);
I ended up using the static Validator class from the DataAnnotations namespace. My test now looks like this:
[TestMethod]
public void PhoneNumberIsValid()
{
var dude = new Person();
dude.PhoneNumber = "666-978-6410";
var result = Validator.TryValidateObject(dude, new ValidationContext(dude, null, null), null, true);
Assert.IsTrue(result);
}
Just new up a RegularExpressionAttribute object.
var regularExpressionAttribute = new RegularExpressionAttribute("pattern");
Assert.IsTrue(regularExpressionAttribute.IsValid(objToTest));
Sorry for answering late.
I'm new here. If you want test every ValidationAttribute in isolate you can proceed to the next manner for example:
[Test]
public void Test_the_State_value_IsRequired()
{
string value = "Finished";
var propertyInfo = typeof(TimeoffTemporalIncapacityEntry).GetProperty("State");
var attribute = propertyInfo.GetCustomAttributes(typeof(RequiredAttribute), true).Cast<RequiredAttribute>().FirstOrDefault();
Assert.IsTrue(attribute.IsValid(value));
}
I used the #Martin 's suggestion along with a static constants file which allowed me to avoid specifing the regex string locally
[TestMethod]
public void Test_Regex_NationalinsuranceNumber()
{
var regularExpressionAttribute = new RegularExpressionAttribute(Constants.Regex_NationalInsuranceNumber_Validate);
List<string> validNINumbers = new List<string>() { "TN311258F", "QQ123456A" };
List<string> invalidNINumbers = new List<string>() { "cake", "1234", "TS184LZ" };
validNINumbers.ForEach(p => Assert.IsTrue(regularExpressionAttribute.IsValid(p)));
invalidNINumbers.ForEach(p => Assert.IsFalse(regularExpressionAttribute.IsValid(p)));
}
You can use this class for validate any ValidationAttribute type in isolate:
T = class type containing the property,
A = type ValidationAttribute
Example:
string stateValue = "Pendiente";
ValidationAttributeValidator<ConfirmationTemporalIncapacityEntry, RequiredAttribute> validator =
new ValidationAttributeValidator<ConfirmationTemporalIncapacityEntry, RequiredAttribute>();
Assert.IsTrue(validator.ValidateValidationAttribute("State", stateValue));
public class ValidationAttributeValidator<T,A>
{
public ValidationAttributeValidator() { }
public bool ValidateValidationAttribute(string property, object value)
{
var propertyInfo = typeof(T).GetProperty(property);
var validationAttributes = propertyInfo.GetCustomAttributes(true);
if (validationAttributes == null)
{
return false;
}
List<ValidationAttribute> validationAttributeList = new List<ValidationAttribute>();
foreach (object attribute in validationAttributes)
{
if (attribute.GetType() == typeof(A))
{
validationAttributeList.Add((ValidationAttribute)attribute);
}
}
return(validationAttributeList.Exists(x => x.IsValid(value)));
}
}
Building on #Evelio's answer I am going to provide an answer to how do you unit test custom validators since this doesn't seem to be articulated anywhere on the internet and this is one of the top hits that come up when searching for how to do it.
#Evelio's answer is very close, but it could do with a bit more of an explanation.
To test your validation you need to have a class that attaches validation attributes to its member data. Here I am using a new custom validator that makes sense for my project called FeeTimeUnitValidator. This validator takes a range and another attribute as input. If the other attribute is zero, then the attribute the validator is attached to doesn't matter. But if the other attribute is not zero, then this attribute needs to be in the range.
Here is the MockClass I use for testing:
class MockClass
{
public decimal Fee { get; set; }
[FeeTimeUnitValidator(otherPropertyName:"Fee", minValue:1, maxValue:12)]
public int attributeUnderTest { get; set; }
public int badOtherProperty { get; set; }
[FeeTimeUnitValidator(otherPropertyName: "badOtherProperty", minValue: 1, maxValue: 12)]
public int badAttributeUnderTest { get; set; }
[FeeTimeUnitValidator(otherPropertyName: "NotFoundAttribute", minValue: 1, maxValue: 12)]
public int nameNotFoundAttribute { get; set; }
}
Notice the attribute validation:
[FeeTimeUnitValidator(otherPropertyName:"Fee", minValue:1, maxValue:12)]
This says to check the property "Fee" as the Fee property (i.e., it has to be non-zero) and then the range is 1 - 12.
I instantiate class in the unit test class and set it up with a setup method. Since there are three attributes on this class that have the validator, I pass in the name of the attribute into the setup class.
private MockClass classUnderTest;
private ValidationContext context;
FeeTimeUnitValidator setup(string attributeUnderTest)
{
classUnderTest = new MockClass();
classUnderTest.Fee = 0;
var propertyInfo = typeof(MockClass).GetProperty(attributeUnderTest);
var validatorArray = propertyInfo.GetCustomAttributes(typeof(FeeTimeUnitValidator), true);
Assert.AreEqual(1, validatorArray.Length);
var validator = validatorArray[0];
Assert.IsTrue(validator.GetType().Equals(typeof(FeeTimeUnitValidator)));
context = new ValidationContext(classUnderTest, null, null);
return (FeeTimeUnitValidator)validator;
}
There are a few things of interest. I am using #Evelio's approach to extract the validator from the attribute. This is doe in lines 3 and 4 of the setup routine. Then, since this is a unit test method, I do some asserts to make sure that I got what I expected. This actually caught a problem when I transferred this pattern to another unit test class for another validator.
Then the other key is that I create the ValidationContext (since the more complicated validators need a context to find the other attributes they refer to - in my case I use it to find the Fee attribute). When I was researching how to unit test these custom validators, what was tripping me up was the ValidationContext. I couldn't find any information about how to create them. I believe the "context" for the attribute validation is the class in which the attribute lives. This is why I create the validation context with the class instance as the first parameter. This then provides the validator with access to the other attributes on the class so you can do cross attribute validation.
Now that i have the context created and a pointer to a validator, I can jump into the unit test itself to ensure that the validator is doing its job properly:
[TestMethod]
public void TestInRangeIsValidWhenFeeNonZero()
{
// Arrange
var validator = setup("attributeUnderTest");
classUnderTest.Fee = 10;
// Act
ValidationResult value12 = validator.GetValidationResult(12, context);
ValidationResult value1 = validator.GetValidationResult(1, context);
ValidationResult value5 = validator.GetValidationResult(5, context);
// Assert
Assert.AreEqual(ValidationResult.Success, value12);
Assert.AreEqual(ValidationResult.Success, value1);
Assert.AreEqual(ValidationResult.Success, value5);
}
If my validator didn't need a context (i.e., it could validate the attribute without reference to the other attributes), then I could use the simpler interface of IsValid(), but if the validator needs a non-null context, you have to use the GetValidationResult() method like I have done here.
I hope this helps somebody else who might be writing validators and is as religious about unit testing as I am. :)
Here is a good article on creating custom validators.
Extending on #CobraGeek's answer and #Erik's comment, you can use the Validator.TryValidateProperty to validate only that one field instead of the whole object, as so:
var results = new List<ValidationResult>();
Person dude = new Person();
System.ComponentModel.TypeDescriptor.AddProviderTransparent
(new AssociatedMetadataTypeTypeDescriptionProvider(dude.GetType()), dude.GetType());
dude.PhoneNumber = "555-867-5309";
var vc = new ValidationContext(dude, null, null);
vc.MemberName = "PhoneNumber";
bool result = Validator.TryValidateProperty(dude.PhoneNumber, vc, results);
After which result is the boolean indicating success of the validation, and if false results contains the list of the details of the errors thrown.
// You can do something like this.
[TestMethod]
public void PhoneNumberIsValid
{
var propInfo = typeof(Person).GetProperty("PhoneNumber");
var attr = propInfo.GetCustomAttributes(typeof(RegularExpressionAttribute), true);
// Act Assert Positives
Assert.IsTrue(((RegularExpressionAttribute)attr [0]).IsValid("555-55-5555"));
// Act Assert Negative
Assert.IsFalse(((RegularExpressionAttribute)attr[0]).IsValid("123654654654"));
}

How can I unit test my custom validation attribute

I have a custom asp.net mvc class validation attribute.
My question is how can I unit test it?
It would be one thing to test that the class has the attribute but this would not actually test that the logic inside it. This is what I want to test.
[Serializable]
[EligabilityStudentDebtsAttribute(ErrorMessage = "You must answer yes or no to all questions")]
public class Eligability
{
[BooleanRequiredToBeTrue(ErrorMessage = "You must agree to the statements listed")]
public bool StatementAgree { get; set; }
[Required(ErrorMessage = "Please choose an option")]
public bool? Income { get; set; }
.....removed for brevity
}
[AttributeUsage(AttributeTargets.Class)]
public class EligabilityStudentDebtsAttribute : ValidationAttribute
{
// If AnyDebts is true then
// StudentDebts must be true or false
public override bool IsValid(object value)
{
Eligability elig = (Eligability)value;
bool ok = true;
if (elig.AnyDebts == true)
{
if (elig.StudentDebts == null)
{
ok = false;
}
}
return ok;
}
}
I have tried to write a test as follows but this does not work:
[TestMethod]
public void Eligability_model_StudentDebts_is_required_if_AnyDebts_is_true()
{
// Arrange
var eligability = new Eligability();
var controller = new ApplicationController();
// Act
controller.ModelState.Clear();
controller.ValidateModel(eligability);
var actionResult = controller.Section2(eligability,null,string.Empty);
// Assert
Assert.IsInstanceOfType(actionResult, typeof(ViewResult));
Assert.AreEqual(string.Empty, ((ViewResult)actionResult).ViewName);
Assert.AreEqual(eligability, ((ViewResult)actionResult).ViewData.Model);
Assert.IsFalse(((ViewResult)actionResult).ViewData.ModelState.IsValid);
}
The ModelStateDictionary does not contain the key for this custom attribute.
It only contains the attributes for the standard validation attributes.
Why is this?
What is the best way to test these custom attributes?
Your attribute EligabilityStudentDebtsAttribute is just a standard class, like everything else, just unit test the IsValid() method. If it works OK, trust to Framework that attribute works OK.
So:
[Test]
public void AttibuteTest()
{
// arrange
var value = //.. value to test - new Eligability() ;
var attrib = new EligabilityStudentDebtsAttribute();
// act
var result = attrib.IsValid(value);
// assert
Assert.That(result, Is.True)
}
Your custom validation attribute might be dependent on the state of other properties. In this case you can use the System.ComponentModel.DataAnnotations.Validator static methods, for example:
var model = ...
var context = new ValidationContext(model);
var results = new List<ValidationResult>();
var isValid = Validator.TryValidateObject(model, context, results, true);
Assert.True(isValid);
I have found out that IsValid does not work well on simple types like string. E.g. if you have a validation on a string query parameter that is not an object. Additionally it's easier to test a value directly on the attribute without having to provide a whole object. It also allows to check the error message. This is how it works:
string input = "myteststring";
var myAttribute = new MyAttribute()
var result = attribute.GetValidationResult(input, new ValidationContext(input));
var isSuccess = result == ValidationResult.Success;
var errorMessage = result?.ErrorMessage;
This code tests only the validation of your input value and nothing else.
P.S. I have tested this in dotnet core, but I would think this works for ordinary dotnet as well.

Categories

Resources