Monday, March 26, 2018
Boise Code Camp Presentation on Into to Continues Integration
Slides from my Into to TDD talk at Boise Code camp
Tuesday, February 23, 2016
Unit Testing Asp.net MVC View Model Validation
ViewModel attribute validation is a key feature of ASP.NET MVC, but is often not a tested one. For the most part developers verify controller actions, pass in a model, mock the ModelState.IsValid property and call it good, never checking to make sure if the model is validating correctly. One of the biggest reasons is developers just don’t know how, it’s not difficult, but it’s not very intuitive so here is a quick tutorial.
First we take a view model with some basic validation attributes
1: public class RegisterViewModel
2: {
3: [Required]
4: [EmailAddress]
5: [Display(Name = "Email")]
6: public string Email { get; set; }
7:
8: [Required]
9: [StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)]
10: [DataType(DataType.Password)]
11: [Display(Name = "Password")]
12: public string Password { get; set; }
13:
14: [DataType(DataType.Password)]
15: [Display(Name = "Confirm password")]
16: [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
17: public string ConfirmPassword { get; set; }
18: }
Next we create a simple reusable testing context class
1: public abstract class ViewModelValidation_Context<T> where T : new()
2: {
3: public T Target;
4: public List<ValidationResult> ActualMessages;
5: public bool Actual;
6:
7: [SetUp]
8: public void SetUp()
9: {
10: Context();
11: Because();
12: }
13:
14: public virtual void Context()
15: {
16: Target = new T();
17: ActualMessages = new List<ValidationResult>();
18: }
19:
20: public virtual void Because()
21: {
22: var context = new ValidationContext(Target, null, null);
23: Actual = Validator.TryValidateObject(Target, context, ActualMessages, true);
24: }
25: }
Notice this test context class is a little different than what I have used in the past, we are predefining the Because() method. In this case regardless of the class we are testing we are still going to run what is really the meat of this class, we create a Validation Context and then passes it to System.ComponentModel.DataAnnotations.Validator.TryValidateObject and this returns if the view model passed validation and and validation messages.
Next lets put this into action with a test to validate our model
1: [TestFixture]
2: public class When_Testing_RegisterViewModel_Validation_Test : ViewModelValidation_Context<RegisterViewModel>
3: {
4: public override void Context()
5: {
6: base.Context();
7: Target.Email = "TestEmail@email.com";
8: Target.Password = "TestPass";
9: Target.ConfirmPassword = "TestPass";
10: }
11:
12: [Test]
13: public void Passed_Validation_Test()
14: {
15: Assert.IsTrue(Actual);
16: }
17:
18: [Test]
19: public void Has_No_ValidationMessages_Test()
20: {
21: Assert.IsFalse(ActualMessages.Any());
22: }
23: }
The model validation requires we have a valid email, we have a password, a confirm password, and password and confirm password match, in this case everything is good and we get a true for is valid and no validation massages.
Next lets test an invalid model
1: [TestFixture]
2: public class When_Testing_RegisterViewModel_Validation_With_NoPassword_Test : ViewModelValidation_Context<RegisterViewModel>
3: {
4: public override void Context()
5: {
6: base.Context();
7: Target.Email = "TestEmail@email.com";
8: Target.Password = string.Empty;
9: Target.ConfirmPassword = "TestPass";
10: }
11:
12: [Test]
13: public void Fail_Validation_Test()
14: {
15: Assert.IsFalse(Actual);
16: }
17:
18: [Test]
19: public void Has_No_ValidationMessages_Test()
20: {
21: Assert.IsTrue(ActualMessages.Any());
22: }
23:
24: [TestCase("The Password field is required.")]
25: [TestCase("The password and confirmation password do not match.")]
26: public void Has_Expected_Validation_ErrorMessages_Test(string message)
27: {
28: Assert.IsTrue(ActualMessages.Any(x=>x.ErrorMessage== message));
29: }
30:
31: [TestCase("Password")]
32: public void Has_Expected__ValidationMessages_Test(string memberName)
33: {
34: Assert.IsTrue(ActualMessages.Any(x => x.MemberNames.Contains(memberName)));
35: }
36: }
In this test we made password an empty string and as a result it failed validation, and returned a validation message for password being required and for password and confirm password not matching.
Check out my sample application on Github to see this in action.
Thursday, November 28, 2013
Testing un-mockable base class methods
To get around this I used a lazy load property and a delegate to create a wrapper.
1: private StartActivityDelegate _start;2: public StartActivityDelegate Start
3: {
4: get { return _start ?? (_start = StartActivity); }
5: set { _start = value; }
6: }
7:
8: public delegate void StartActivityDelegate (Type type);
9:
10: protected override void OnCreate(Bundle bundle)
11: {
12: base.OnCreate(bundle);
13: Redirect();
14: }
15:
16: public void Redirect()
17: {
18: if (Preferences.HasRequired())
19: {
20: Start(typeof(ClientActivity));
21: }
22: else
23: {
24: Start(typeof(SettingsActivity));
25: }
26: }
1: [TestFixture]
2: public class When_Starting_SplashScreen_Without_Required_Preferences_Test
3: {
4: public SplashActivity Target;
5: public Type Actual;
6: public PreferencesMock PreferencesMock;
7:
8: public void TestStartActivity(Type type)
9: {
10: Actual = type;
11: }
12:
13: [SetUp]
14: public void SetUp()
15: {
16: PreferencesMock = new PreferencesMock { HasRequiredStub = false };
17:
18: Target = new SplashActivity { Start = TestStartActivity, Preferences = PreferencesMock };
19: Target.Redirect();
20: }
21:
22: [Test]
23: public void Has_Expected_Activity_Test()
24: {
25: Assert.AreEqual(typeof(SettingsActivity), Actual);
26: }
27: }
1: [TestFixture]
2: public class SplashActivity_Tests
3: {
4: public SplashActivity Target;
5:
6: [SetUp]
7: public void SetUp()
8: {
9: Target = new SplashActivity();
10: }
11:
12: [Test]
13: public void LazyLoads_Write_Test()
14: {
15: MethodInfo actual = Target.Start.GetMethodInfo();
16: MethodInfo expected = typeof(Activity).GetMethod("StartActivity");
17: Assert.AreSame(expected, actual);
18: }
19: }
Tuesday, January 8, 2013
Tools only give you the ability
Doing unit testing with mocking is a tool, not a silver bullet. Unit tests mixed with behavior testing can be very effective at finding these kinds of bugs and Mocks allow you to create and test against foreseen conditions. While TDD can help expose bugs, unforeseen data conditions can pass though unnoticed, tests only test what they are told to.
Something else to keep in mind is behavior and unit tests are only a small part of the testing tool box that also includes: integration tests, performance tests, automated UI tests, etc. and that’s what these are tools. How effective they are depends on the user, a funny quote I once heard is “A tool runs tools, a craftsman uses them”. You can run your code coverage tool all day long and say “look I have 80% code coverage” but this doesn't mean your code is well tested, it just means it has test that run though it, and can hide untested code.
One commenter on the article said something to the effect that what they where working on requires to much performance to be testable. I would like to state this is B.S. you can make testable code that has just as much performance as not testable code. You may have to change how you test the code, or use different design or testing techniques for example Dependency Injection isn't required to make testable code, if it's not fast enough use greedy constructors or lazy load properties. If you need to make a black box class for performance, you simply do integration tests around it.
In the end, saying TDD isn’t of value because it didn’t catch bug xyz is like a carpenter blaming his hammer for hitting his thumb and not the nail. On the other side it’s just as ridiculous to say “I can build a house” simply because you own a hammer and some nails.
Wednesday, June 27, 2012
Running your test while you write them using NCrunch
The basic idea is NCrunch automatically runs your tests in a parallel process giving you continuous testing that’s integrated into Visual Studio. It intelligently runs tests automatically so that you don't have to, and gives you useful information including code coverage and performance metrics while you work.
Here is an example of the inline test results
here is a screenshot of the test runner
and this what the coverage looks like
it’s not quite as detailed as I would like, but not bad.
This isn’t going to be a replacement for my copy of Resharper and DotCover but at the same time, I don’t see a problem with running both. This could defiantly save me a lot of time waiting for test to run and if I want more detailed information I can still use dotCover.
This is a free tool that so far has really impressed me, for more information go to the NCrunch home page athttp://www.ncrunch.net/
Monday, June 25, 2012
Testing for execution sequence
Thought it would be interesting to show the solution I used for testing, lets say we have a simple method for updating an employee record.
1: public bool UpdateEmployee(EmployeeDto updateEmployee)
2: {
3: var result = false;
4: if(AuthRequests.UserCanUpdateEmployee(User.Id,updateEmployee.Id))
5: {
6: result = EmployeeRepository.UpdateEmployee(updateEmployee);
7: }
8: return result;
9: }
1: [TestFixture]
2: public class When_Updating_Employee_Information_Test: Test_Context<EmployeeRequests>
3: {
4: public Mock<IAuthRequests> AuthRequestsMock;
5: public bool ExpectedAuth;
6: public int UserId;
7: public int EmployeeId;
8: public bool Actual;
9: public bool Expected;
10: public EmployeeDto UpdateEmployee;
11: public EmployeeDto SentEmployee;
12: public UserDto ExpectedUser;
13: public Mock<IEmployeeRepository> MockEmployeeRepository;
14:
15: public override void Context()
16: {
17: base.Context();
18: UserId = 12;
19: EmployeeId = 14;
20: Expected = true;
21: ExpectedAuth = true;
22: UpdateEmployee = new EmployeeDto{Id = EmployeeId};
23: Target.User.Id = UserId;
24: AuthRequestsMock = new Mock<IAuthRequests>();
25: MockEmployeeRepository = new Mock<IEmployeeRepository>();
26:
27: AuthRequestsMock.Setup(x => x.UserCanUpdateEmployee(UserId, EmployeeId)).Returns(ExpectedAuth);
28: MockEmployeeRepository.Setup(x => x.UpdateEmployee(It.IsAny<EmployeeDto>())).Callback<EmployeeDto>(
29: x => SentEmployee = x).Returns(Expected);
30:
31: Target.AuthRequests = AuthRequestsMock.Object;
32: Target.EmployeeRepository = MockEmployeeRepository.Object;
33: }
34:
35: public override void Because()
36: {
37: Actual = Target.UpdateEmployee(UpdateEmployee);
38: }
39:
40: [Test]
41: public void Calls_AuthRequests_UserCanUpdateEmployee_Test()
42: {
43: AuthRequestsMock.Verify(x=>x.UserCanUpdateEmployee(UserId, EmployeeId), Times.Once());
44: }
45:
46: [Test]
47: public void Calls_EmployeeRepository_UpdateEmployee_Test()
48: {
49: MockEmployeeRepository.Verify(x => x.UpdateEmployee(It.IsAny<EmployeeDto>()), Times.Once());
50: }
51:
52: [Test]
53: public void Sends_expected_Emplyee_test()
54: {
55: Assert.AreEqual(UpdateEmployee.Id, SentEmployee.Id);
56: }
57:
58: [Test]
59: public void Returns_Expected_Result_Test()
60: {
61: Assert.IsTrue(Actual);
62: }
63: }
1: public bool UpdateEmployee(EmployeeDto updateEmployee)
2: {
3: var result = EmployeeRepository.UpdateEmployee(updateEmployee);
4: AuthRequests.UserCanUpdateEmployee(User.Id,updateEmployee.Id);
5: return result;
6: }
1: [TestFixture]
2: public class When_Updating_Employee_Information_With_sequence_Test : Test_Context<EmployeeRequests>
3: {
4: public Mock<IAuthRequests> AuthRequestsMock;
5: public bool ExpectedAuth;
6: public int UserId;
7: public int EmployeeId;
8: public bool Actual;
9: public bool Expected;
10: public EmployeeDto UpdateEmployee;
11: public EmployeeDto SentEmployee;
12: public UserDto ExpectedUser;
13: public Mock<IEmployeeRepository> MockEmployeeRepository;
14: public int Counter;
15: public Dictionary<string, int> Sequence;
16:
17: public override void Context()
18: {
19: base.Context();
20: UserId = 12;
21: EmployeeId = 14;
22: Expected = true;
23: ExpectedAuth = true;
24: UpdateEmployee = new EmployeeDto { Id = EmployeeId };
25: Counter = 0;
26: Sequence = new Dictionary<string, int>();
27: Target.User.Id = UserId;
28: AuthRequestsMock = new Mock<IAuthRequests>();
29: MockEmployeeRepository = new Mock<IEmployeeRepository>();
30:
31: AuthRequestsMock.Setup(x => x.UserCanUpdateEmployee(UserId, EmployeeId))
32: .Callback(() => Sequence.Add("AuthRequests.UserCanUpdateEmployee", Counter++)).Returns(ExpectedAuth);
33: MockEmployeeRepository.Setup(x => x.UpdateEmployee(It.IsAny<EmployeeDto>()))
34: .Callback<EmployeeDto>(x =>
35: {
36: SentEmployee = x;
37: Sequence.Add("EmployeeRepository.UpdateEmployee", Counter++);
40: Target.AuthRequests = AuthRequestsMock.Object;
41: Target.EmployeeRepository = MockEmployeeRepository.Object;
42: }
43:
44: public override void Because()
45: {
46: Actual = Target.UpdateEmployee(UpdateEmployee);
47: }
48:
49: [Test]
50: public void Calls_AuthRequests_UserCanUpdateEmployee_Test()
51: {
52: AuthRequestsMock.Verify(x => x.UserCanUpdateEmployee(UserId, EmployeeId), Times.Once());
53: }
54:
55: [Test]
56: public void Calls_AuthRequests_UserCanUpdateEmployee_Inorder_Test()
57: {
58: Assert.AreEqual(0, Sequence["AuthRequests.UserCanUpdateEmployee"]);
59: }
60:
61: [Test]
62: public void Calls_EmployeeRepository_UpdateEmployee_Test()
63: {
64: MockEmployeeRepository.Verify(x => x.UpdateEmployee(It.IsAny<EmployeeDto>()), Times.Once());
65: }
66:
67: [Test]
68: public void Calls_EmployeeRepository_UpdateEmployee_Inorder_Test()
69: {
70: Assert.AreEqual(1, Sequence["EmployeeRepository.UpdateEmployee"]);
71: }
72:
73: [Test]
74: public void Sends_expected_Emplyee_test()
75: {
76: Assert.AreEqual(UpdateEmployee.Id, SentEmployee.Id);
77: }
78:
79: [Test]
80: public void Returns_Expected_Result_Test()
81: {
82: Assert.IsTrue(Actual);
83: }
84: }