This article is cross-posted from my personal blog.
In the previous article in this blog series on creating testable modules, I created a simple MVP based application. In this article I will add a Test project to the solution and some tests. Figure 1 shows the Test project as it looks in Visual Studio’s Solution Explorer.
Figure 1 – The Test Project
In this example project there are three classes, the Test class itself (HellowWorldMVPTests.cs) a Mock class (MockView.cs) and a utility class (Constants.cs).
There are many testing frameworks we could use. For convenience, I will be using MSTest, which is the default Unit Test Framework in Visual Studio. When you create a new Test project in Visual Studio, a single class file is added which is already set up ready for writing tests. In MSTest a test class is identified by the class-level attribute “TestClass” (see Listing 1).
Listing 1 – The Test Class before adding tests
|
1: [TestClass]
2: public class HeloWorldMVPTests
3: {
4:
5: }
|
Writing our First Test
So now we have a Test Project, lets write the first test. If we go back to our specifications in the previous article, we will see that one of the specifications was “On initially browsing to the page the label will give the user instructions”, so lets start by writing a test to simulate that specification.
The tests I will be writing are designed to demonstrate that the Presenter is correctly setting the properties of the View. As the real View is a User Control it is not easily instantiated outside of a WebForm, so first, I will create a Mock View class to simulate the behavior of a View and allow us to test the Presenter (see Listing 1).
Listing 1 - the MockView class
|
1: class MockView : IView
2: {
3: private string text;
4: private string label;
5:
6: public MockView()
7: {
8: }
9:
10: public MockView(string text)
11: {
12: this.text = text;
13: }
14:
15: public string GetLabelContent()
16: {
17: return label;
18: }
19:
20: public string Label
21: {
22: set { label = value; }
23: }
24:
25: public string Text
26: {
27: get { return text; }
28: }
29: }
|
The MockView class implements the same IView interface as the HelloWorldView class - a write-only string property - Label, and a read-only string property - Text. The MockView has two constructors - the reason for this will become apparent later, and a helper method that returns the value of the private member label - this method enables our test class to determine if the value of the label was set properly, as the Label property is write-only.
Our initial test is shown in Listing 2. Note that as with the class, the method is decorated with an attribute - "TestMethod" - which tells the Visual Studio Unit Test Framework that this method is a Test.
Listing 2 – The first Test
|
1: [TestMethod]
2: public void View_Should_Display_IntroText_When_First_Loaded()
3: {
4: MockView view = new MockView();
5: Presenter presenter = new Presenter(view);
6:
7: //Call the presenters OnViewLoaded method to simulate the
8: //Page Load (isPostBack = false)
9: presenter.OnViewLoaded(false);
10:
11: //Assert that the View displays the IntroText
12: Assert.AreEqual(Constants.IntroText, view.GetLabelContent());
13: }
|
I have named the test method View_Should_Display_IntroText_When_First_Loaded. This is purely a convention, but this name makes it clear what passed and what failed when the test results are listed. This test is designed to verify that the View will display the introductory text when the page is first loaded.
In line 4 of the test the MockView is created. As the default constructor is used, the View's text property starts off empty, as the HelloWorldView user control does when it is first displayed. Line 5 creates a Presenter, passing the MockView as a parameter of its constructor. Remember the Presenter’s constructor takes an IView, so any concrete implementation is accepatable.
On line 9 we call the Presenter's OnViewLoaded method, which simulates the View being loaded into the WebForm.
Finally on line 12 we get the guts of the test - we use the Test Framework's Assert class to determine if the View's label is correctly set to the introductory text which is defined in the Constants class (see Listing 3).
Listing 3 - The Constants class
|
1: class Constants
2: {
3: internal const string IntroText =
4: "Enter your name and click the Submit Button";
5: internal const string HelloText =
6: "World";
7: }
|
So that's the first test. If the project is compiled and the test run we should see one green check-mark.
Figure 2 - The results of the First Test
Yes - our test passed, now we can celebrate!
The Remaining Tests
Its always great to see those check marks, but this is just one test. Figure 3 shows the code coverage results for this test.
Figure 3 - Code Coverage for the First Test
This one test covers a little less than 60% of the code in the Presenter class. In order to cover 100% of the Presenter class we need to add some more tests.
Two possible tests stand out from the initial specifications - "When the user clicks the Submit button, the label is updated to display ‘Hello ‘ plus the text the user enters in the text box" and "When the user clicks the Reset button, the label is reset to display the initial instructions".
The tests for these two specifications are shown implemented in two new test methods (see Listing 4)
Listing 4 - The Remaining Tests
|
1: [TestMethod]
2: public void View_Should_Display_HelloText_When_Submit_Button_Is_Clicked()
3: {
4: MockView view = new MockView(Constants.HelloText);
5: Presenter presenter = new Presenter(view);
6:
7: //Call presenter's Update method to simulate the Submit button
8: presenter.Update();
9:
10: //Assert that the View displays the HelloText
11: Assert.AreEqual("Hello - " + Constants.HelloText, view.GetLabelContent());
12: }
13:
14: [TestMethod]
15: public void View_Should_Display_IntroText_When_Reset_Button_Is_Clicked()
16: {
17: MockView view = new MockView(Constants.HelloText);
18: Presenter presenter = new Presenter(view);
19:
20: //Call presenter's Reset method to simulate the Reset button
21: presenter.Reset();
22:
23: //Assert that the View displays the IntroText
24: Assert.AreEqual(Constants.IntroText, view.GetLabelContent());
25: }
|
The first test View_Should_Display_HelloText_When_Submit_Button_Is_Clicked verifies that when the submit button is clicked the label will be updated to display "Hello " plus whatever the user entered in the text box. The second test verifies that when the reset button is clicked the label displays the introductory text, even if there is text in the text box - View_Should_Display_IntroText_When_Reset_Button_Is_Clicked.
These two tests demonstrate the use of the extra overload on the MockView. As the IView interface only exposes the Text property as read-only, we have no mechanism to set the value - to simulate the user entering text. We therefore use the constructor overload to set the value.
Figures 4 and 5 show the test results and code coverage for the test project with all three tests. All three tests passed and we now have 100% code coverage for our Presenter class. This gives us a high degree of confidence that the code is correct.
Figure 4 - Test Results for all Three Tests
Figure 5 - Code Coverage for all Three Tests
Note: We do not have 100% code coverage of the whole HelloWorldMVP project, but the difference with this approach, compared with the more conventional WebForm design, is that most of our logic has been removed from the UserControl’s code behind and placed in a separate class – the Presenter – which we can test with 100% code coverage. Our UserControl – the View – now consists of simple properties and event handlers.
This is just a simple example - but I hope it demonstrates most of the principles we will use as we build our Testable DotNetNuke Module.