DNN Community Blog

The Community Blog is a personal opinion of community members and by no means the official standpoint of DNN Corp or DNN Platform. This is a place to express personal thoughts about DNNPlatform, the community and its ecosystem. Do you have useful information that you would like to share with the DNN Community in a featured article or blog? If so, please contact .

The use of the Community Blog is covered by our Community Blog Guidelines - please read before commenting or posting.


Creating Testable Modules - DataService

This blog post is cross-posted from my personal blog.

In the previous article in this blog series on creating testable modules, I added the Repository class to the Business Layer of our LinksMVP module.  In this article I will add the concrete DataService class.  As I mentioned in the previous article I propose to use the DAL+ methods.

DataService class

Listing 1 shows the DataService class.  It contains two private variables, dataprovider which is a reference to the core DataProvider, and moduleQualifer which is a constant we will use the provide a unique name for the stored procedure.

Listing 1 – The DataService class

   1:  public class DataService : IDataService
   2:  {
   3:      private DataProvider dataProvider = DataProvider.Instance();
   4:      private string moduleQualifier = "LinksMVP_";
   5:   
   6:      public IDataReader GetLinks(int moduleId)
   7:      {
   8:          return dataProvider.ExecuteReader(String.Concat(moduleQualifier, "GetLinks"), moduleId);
   9:      }
  10:  }

The GetLinks method is very simple – it calls the core DataProvider’s ExecuteReader method, passing the qualified name of the stroed procedure (ie LinksMVP_GetLinks) and the module Id.

Testing the Data Layer

The Data Layer and the Views are the hardest parts of the module to test.  The approach I am going to take in testing the DataService class is to create a test database in code before running the tests and then to tear down the database after the tests have been run.

Listing 2 – The DataServiceTests Class’s Constructor

   1:  public DataServiceTests()
   2:  {
   3:      //Create a Container
   4:      ComponentFactory.Container = new SimpleContainer();
   5:   
   6:      // Build the Connection String for the administration stuff (create/drop test DB)
   7:      adminConnectionString = String.Format("Data Source={0};Integrated Security=True", ServerName);
   8:   
   9:      // Build the Connection String for the test
  10:      connectionString = String.Format("Data Source={0};Integrated Security=True;Initial Catalog={1}",
                                             ServerName, DatabaseName);
  11:   
  12:      //Register the Settings for the SqlDataProvider
  13:      Dictionary<string, string> settings = new Dictionary<string, string>();
  14:      settings["providerName"] = providerName;
  15:      settings["providerPath"] = providerPath;
  16:      settings["connectionString"] = connectionString;
  17:      settings["databaseOwner"] = databaseOwner;
  18:      settings["objectQualifier"] = objectQualifier;
  19:      settings["upgradeConnectionString"] = "";
  20:   
  21:      //Register the Settings
  22:      ComponentFactory.Container.RegisterComponentSettings("DotNetNuke.Data.SqlDataProvider", 
                                                                settings);
  23:   
  24:      //Register the DataProvider with the Container
  25:      ComponentFactory.Container.RegisterComponentInstance(new SqlDataProvider(false));
  26:  }

In the Test classes constructor method, I create a Container to hold the DotNetNuke Data Provider.  This mimics the behaviour of the DotNetNuke Application_Start method, which loads all the providers into the Container.  In this case we are not configuring the provider from the web.config class, so we create a Dictionary load all the settings and register the Dictionary in the Container, before we register the SqlDataProvider class itself.  We have to do the registration in this order as the SqlDataProvider class’s Constructor expects the settings Dictionary to be loaded.

As with the Repository Tests, where we had to load a Caching Provider, we are now ready to “run” our tests, from a code point of view.  However, we still need a database to test with.

Setting up the Database for Testing

The testing framework provides an attribute TestInitialize that allows you to mark a method that should run before all the tests.  We will use this Attribute to mark a method that we will use to create the Database, and add the Links Table (see Listing 3).

Listing 3 – The TestInitiliaze Method

   1:  [TestInitialize]
   2:  public void TestInitialize()
   3:  {
   4:      // Connect to the server to create the database
   5:      using (SqlConnection connection = new SqlConnection(adminConnectionString))
   6:      {
   7:          connection.Open();
   8:   
   9:          CreateDatabase(connection);
  10:      }
  11:   
  12:      // Connect to the database to create the tables
  13:      CreateObject(SqlScripts.CreateTable, linksTable);
  14:  }

CreateDatabase (Listing 4) creates the Database using the adminConnectionString (a private constant that is set to the connection string required to connect to your Database Server).  The SQL to create the Database is stored in a resource file (Figure 1) and accessed using SqlScripts.CreateDatabase.

Listing 4 – The CreateDatabase Method

   1:  private void CreateDatabase(SqlConnection connection)
   2:  {
   3:      // Create the database
   4:      ExecuteScript(connection, String.Format(SqlScripts.CreateDatabase, DatabaseName));
   5:   
   6:      // Verify that it was created
   7:      TestDatabaseAction(connection, SqlScripts.DatabaseCount, DatabaseName, 1);
   8:  }

Figure 1 – The SqlScripts.resx Resource File

The script is executed in the helper method ExecuteScript (Listing 5) and then the action is verified using the helper method TestDatabaseAction (Listing 6).

Listing 5 – The ExecteScript Method

   1:  private void ExecuteScript(SqlConnection connection, string sqlScript)
   2:  {
   3:      // Create the object
   4:      using (SqlCommand cmd = connection.CreateCommand())
   5:      {
   6:          cmd.CommandText = RemoveTokens(sqlScript);
   7:          cmd.ExecuteNonQuery();
   8:      }
   9:  }

Listing 6 – The TestDatabaseAction Method

   1:  private void TestDatabaseAction(SqlConnection connection, string sqlScript, 
                                               string objectName, int count)
   2:  {
   3:      using (SqlCommand cmd = connection.CreateCommand())
   4:      {
   5:          if (!String.IsNullOrEmpty(objectName))
   6:              sqlScript = String.Format(sqlScript, objectName);
   7:          cmd.CommandText = sqlScript;
   8:          Assert.AreEqual(count, cmd.ExecuteScalar());
   9:      }
  10:  }

In a similar way CreateObject (Listing 7) uses the Database connection string, which is again defined as a private constant to run a script that adds a single object to the newly created Database.  We again verify that the expected object was in fact created.

Listing 7 – CreateObject Method

   1:  private void CreateObject(string sqlScript, string objectName)
   2:  {
   3:      // Connect to the database to create the objects
   4:      using (SqlConnection connection = new SqlConnection(connectionString))
   5:      {
   6:          connection.Open();
   7:   
   8:          // Create the object
   9:          ExecuteScript(connection, sqlScript);
  10:   
  11:          // Verify that it was created
  12:          TestDatabaseAction(connection, SqlScripts.ObjectCount, 
  13:              String.Concat(objectQualifier, objectName), 1);
  14:      }
  15:  }

Before writing our tests we can confirm that the Database gets created and the Links table is added by running the tests in the Test Runner.  Our TestDatabaseAction method contains Assert calls to make sure that what we expected did in fact happen.

The GetLinks DataService Test

We are now ready to write our GetLinks Test (see Listing 8)

Listing 8 – The GetLinks DataService Test

   1:  [TestMethod]
   2:  public void DataService_Should_Retrieve_All_Links_When_ModuleId_Is_Valid()
   3:  {
   4:      // Create the Stored procedure to test
   5:      CreateObject(SqlScripts.GetLinks, getLinks);
   6:   
   7:      // Add Links to Database
   8:      using (SqlConnection connection = new SqlConnection(connectionString))
   9:      {
  10:          connection.Open();
  11:   
  12:          // Run the scripts
  13:          for (int i = 1; i < 3; i++)
  14:          {
  15:              string sqlScript = RemoveTokens(SqlScripts.AddLink);
  16:              string moduleId = MockHelper.ValidModuleId.ToString();
  17:              string linkUrl = String.Format(MockHelper.LinkURL, i.ToString());
  18:              string title = String.Format(MockHelper.Title, i.ToString());
  19:              ExecuteScript(connection, String.Format(sqlScript, moduleId,
  20:                                                  linkUrl, title)
  21:                  );
  22:          }
  23:   
  24:          // Verify that the links were added
  25:          TestDatabaseAction(connection, RemoveTokens(SqlScripts.LinksCount), "", 2);
  26:      }
  27:   
  28:   
  29:      //Create the DataService
  30:      DataService dataService = new DataService();
  31:   
  32:      //Call the GetLinks method
  33:      IDataReader dataReader = dataService.GetLinks(MockHelper.ValidModuleId);
  34:      int records = 0;
  35:      while (dataReader.Read())
  36:      {
  37:          records += 1;
  38:      }
  39:   
  40:      dataReader.Close();
  41:   
  42:      //Assert that the count is 2
  43:      Assert.AreEqual<int>(2, records);
  44:  }

In line 4 of Listing 8 we create the GetLinks stored procedure, lines 8 – 26 create two links and add them to the Links table. In line 30 we create an instance of our DataService class and in line 33 we call the GetLinks method.  Lines 34 – 40 analyze the IDataReader returned by the GetLinks method and finally in line 43 we assert that the number of records returned was 2 – the number we added above.  When we run this test it should pass.

Database Cleanup

This isn’t quite the end yet though.  We spent quite a lot of effort setting up a clean Database for our testing.  One of the Best Practices of Unit Testing is that the system should be left in the same state as it was before the test was run.  We therefore need to delete our test Database.

As with test setup,  the testing framework provides an attribute – TestCleanup – which can be applied to a method that will be run by the framework after all the tests have completed – regardless of whether they pass or fail.  In this method we will add code to drop our test Database.

Listing 9 – The TestCleanup Method

   1:  [TestCleanup]
   2:  public void TestCleanup()
   3:  {
   4:      SqlConnection.ClearAllPools();
   5:   
   6:      // Connect to the server
   7:      using (SqlConnection connection = new SqlConnection(adminConnectionString))
   8:      {
   9:          connection.Open();
  10:   
  11:          DropDatabase(connection);
  12:      }
  13:  }

As with the Initialize phase, we use the adminConnectionString to execute SQL to drop the Database (see Listing 10).

Listing 10 – The DropDatabase Method

   1:  private void DropDatabase(SqlConnection connection)
   2:  {
   3:      // Drop the database
   4:      ExecuteScript(connection, String.Format(SqlScripts.DropDatabase, DatabaseName));
   5:   
   6:      // Verify that it was dropped
   7:      TestDatabaseAction(connection, SqlScripts.DatabaseCount, DatabaseName, 0);
   8:  }

Finally, we check that our database was actually dropped.

Now that we have finished the Data Layer for our Get Links feature we have four tests.  Figure 2 shows the results of those four tests and Figure 3 shows the Code Coverage.

Figure 2 – Test Results for LinksMVP

Figure 3 – Code Coverage Results for LinksMVP

We have four passing tests and 100% code coverage – so we can be fairly confident that everything is working so far.

Comments

Comment Form

Only registered users may post comments.

NewsArchives


Aderson Oliveira (15)
Alec Whittington (11)
Alex Shirley (10)
Andrew Nurse (30)
Andy Tryba (1)
Anthony Glenwright (5)
Antonio Chagoury (28)
Ash Prasad (32)
Ben Schmidt (1)
Benjamin Hermann (25)
Benoit Sarton (9)
Beth Firebaugh (12)
Bill Walker (36)
Bob Kruger (5)
Bogdan Litescu (1)
Brian Dukes (2)
Brice Snow (1)
Bruce Chapman (20)
Bryan Andrews (1)
cathal connolly (55)
Charles Nurse (163)
Chris Hammond (209)
Chris Paterra (55)
Clinton Patterson (40)
Cuong Dang (21)
Daniel Bartholomew (2)
Daniel Mettler (154)
Dave Buckner (2)
David Poindexter (4)
David Rodriguez (3)
Doug Howell (11)
Erik van Ballegoij (30)
Ernst Peter Tamminga (74)
Geoff Barlow (10)
George Alatrash (6)
Gifford Watkins (3)
Gilles Le Pigocher (3)
Ian Robinson (7)
Israel Martinez (17)
Jan Blomquist (2)
Jan Jonas (3)
Jaspreet Bhatia (1)
Jenni Merrifield (6)
Joe Brinkman (274)
John Mitchell (1)
Jon Henning (14)
Jonathan Sheely (4)
Jordan Coopersmith (1)
Joseph Craig (2)
Kan Ma (1)
Keivan Beigi (3)
Ken Grierson (10)
Kevin Schreiner (6)
Leigh Pointer (31)
Lorraine Young (60)
Malik Khan (1)
Matthias Schlomann (15)
Mauricio Márquez (5)
Michael Doxsey (7)
Michael Tobisch (3)
Michael Washington (202)
Miguel Gatmaytan (3)
Mike Horton (19)
Mitchel Sellers (28)
Nathan Rover (3)
Navin V Nagiah (14)
Néstor Sánchez (31)
Nik Kalyani (14)
Peter Donker (54)
Philip Beadle (135)
Philipp Becker (4)
Richard Dumas (22)
Robert J Collins (5)
Roger Selwyn (8)
Ruben Lopez (1)
Ryan Martinez (1)
Salar Golestanian (4)
Sanjay Mehrotra (9)
Scott McCulloch (1)
Scott S (11)
Scott Wilkinson (3)
Scott Willhite (97)
Sebastian Leupold (80)
Shaun Walker (237)
Shawn Mehaffie (17)
Stefan Cullmann (12)
Stefan Kamphuis (12)
Steve Fabian (31)
Timo Breumelhof (24)
Tony Henrich (3)
Torsten Weggen (2)
Vicenç Masanas (27)
Vincent Nguyen (3)
Vitaly Kozadayev (6)
Will Morgenweck (40)
Will Strohl (165)
William Severance (5)

Content Layout

Subscribe to DNN Digest

DNN Digest is our monthly email newsletter. It highlights news and content from around the DNN ecosystem, such as new modules and themes, messages from leadership, blog posts and notable tweets. Keep your finger on the pulse of the ecosystem by subscribing.  


Copyright 2017 by DNN Corp Terms of Use Privacy
What is Liquid Content?
Find Out
What is Liquid Content?
Find Out
What is Liquid Content?
Find Out