Click here to Skip to main content
15,867,453 members
Articles / Web Development / ASP.NET

Unit Testing a DotNetNuke Private Assembly Module

Rate me:
Please Sign up or sign in to vote.
4.69/5 (10 votes)
18 Jun 2007CPOL18 min read 82.7K   703   35   18
This article describes how to create unit tests that will test out code for a DotNetNuke custom module.

Introduction

If you're like me, you've tried two separate things and found that you like them. The two things I am talking about are:

  1. building custom DotNetNuke modules, and
  2. using the Unit Testing framework in Microsoft Visual Studio 2005.

However, like canned tuna and pineapple*, initially a lot of people might say that the two don't really go well together, but on further inspection will find that, with a bit of careful blending, they are indeed complimentary.

The main reason for unsuitability is the amount of UI-centric work that tends to soak into DotNetNuke PA Module development. But I think the introduction of unit testing into PA development is a good thing precisely for this very reason - a good unit testing framework in your development will make sure you don't glue the UI and Data layers too closely together. And using Test Driven Development (TDD) pays off, well, at least for me who likes to 'sketch' code - meaning trying a few different things and refactoring to get the finished product. My main focus is always driving the logic down away from the UI layer for reuse and good architecture.

This is not intended to be a posting on the merits of unit testing, understanding unit testing, or even how to create a unit testing project. It assumes the reader:

  1. understands unit testing in Visual Studio and
  2. understands DotNetNuke module development.

If you don't understand either of these topics, you'd be better off reading up on some of the many resources already published.

Note: This article is the first in two parts. The second part can be found at Using Data Driven Unit Testing with DotNetNuke. The second article shows how to use Data-Driven unit testing, but this article needs to be read first to understand.

TDD + DNN = GOOD

Test Driven Development is a good thing when it comes to quality improvements. Like many things, it feels counter intuitive and doesn't give immediate payoffs (in fact, it has an immediate cost when it comes to deliverables development time). But if you're into even mid-level complexity DotNetNuke Module development, you should be looking into it. Especially if you have Unit Testing ability in Visual Studio Team Edition. It provides you, as a developer, the ability to really get in and refactor your code quickly, reducing the need to waterfall-plan everything up front and get your design spot on. It's about reflecting the real world development process instead of a stuffy formalised version which I suspect 0 out of 100 DotNetNuke developments actually follow.. Who really knows what your module is going to look like when you finish writing it? A happy programmer hacking away can actually be more productive than a bored programmer filling out design documents.

In my case, I like to work bottom up, get the database design right, and build the application on top of that. So I generally start with the data structures and then define the access methods, then the UI code that runs on top of that. And this article concentrates on unit testing that part of the development - the definition and development of your Data Access code. If you want repeatable end-to-end testing of your create/update/delete (CRUD) code, you need a suite of unit tests. As is always the nature of unit testing, there is a level of hacking involved in getting it right, but put in the effort and you'll have a base-lined version of working code you can change at will without fear of breaking everything, and allowing you to get on with proper refactoring, instead of walking on glass worried what you might break. This is Test Driven Development at its best (in my opinion - your results may vary!). Just change the code, run the tests, and see what broke. Go back, fix it, re run the tests, repeat until you get the magical green ticks of a successful run of unit tests.

Just recently, whilst halfway through the development of a module, I decided to change the underlying database design. All I had to do was run the unit tests to check if everything still worked correctly afterwards. After a few code adjustments, I was on my way again. Without a battery of pre-written unit tests, I wouldn't have been sure if I had gotten it completely right, and possibly I would have persisted with the old (incorrect) design for fear of having to do too much rework.

Screenshot - Completed Unit Tests

When things run well, you get lots of 'green ticks'

This article naturally revolves around downloadable code, of course, but here's the background on how/why it works, and how to get your own DotNetNuke unit tests up and running. I'm going to assume that the reader has already learnt how to use Unit Testing projects in Visual Studio Team System - the rest isn't going to make much sense unless you have. If you, the reader, are familiar with NUnit rather than the VS2005 Unit Testing Framework, you can probably mod things a bit and get it working in the same way - there really isn't that much difference.

The main problems with running DotNetNuke unit tests are the fact that there are some assumptions in the Core DotNetNuke code that the library is going to be running in the context of a web application - which a unit testing application patently is not.

How to create a DotNetNuke Module Unit Test

Step 0: Download and extract the install package

You will use the libraries and files in steps 2 and 3. You don't need the code to run it, it's just there for the curious, or for people who want to extend it further.

Step 1: Create a Unit Test Project (or open an existing one)

To run DotNetNuke Unit tests, you need a unit testing project. This is easily done with the 'Add new project' menu item. You can either create it in a new solution or add the unit test project to an existing DotNetNuke solution you have.

Step 2: Create a folder for the DNN libraries

Create a \bin folder underneath the root of your unit testing project. This is for referencing the required files. I normally do it like this:

MyModuleProject
\bin (where the DotNetNuke dll's go)
\MyModuleProjectTest
\bin\debug (where the compiled test project will go, not the reference files)

Locate the iFinity.Dnn.Utilities.dll library from the download and copy locally into your \bin folder. Copy all the latest DotNetNuke libraries into the \bin directory - you basically need to do a 'copy DotnetNuke*.dll'. You'll also need Microsoft.ApplicationBlocks.Data.dll. If you have the .pdb files for these DLLs, copy them into the directory as well; it helps with debugging when you can step into the core framework code.

The iFinity.Dnn.Utiltities.dll DLL requires the DotNetNuke libraries to be in the same directory. The download doesn't have a copy because most people run slightly different versions.

Step 3: Add in project references

Once you have copied in all the DLLs, use the 'Project-> Add reference' menu item, reference that file and some of the DotNetNuke libraries. These are the bare minimum libraries you need in your unit testing project:

  • DotNetNuke.dll
  • DotNetNuke.SqlDataProvider.dll
  • DotNetNuke.Caching.BroadcastPollingCachingProvider.dll
  • DotNetNuke.Caching.FileBasedCachingProvider.dll
  • DotNetNuke.Membership.DataProvider.dll
  • DotNetNuke.Membership.SqlDataProvider.dll
  • DotNetNuke.Provider.AspnetProvider.dll
  • Microsoft.ApplicationBlocks.Data.dll
  • YourNewModule.dll (the PA module you are working on - if you have the test project in your module solution, it's best to make this a project reference rather than a file reference).

This list really depends on what testing you are going to do within your unit test - and may need to be worked through until all the correct libraries are referenced.

Step 4: Create an app.config file

Create an app.config file in your unit testing project. You can create one of these with the Project-> Add New Item menu command, then choose 'Application configuration file' as the template type. Or just create an XML file and rename it app.config - either way you'll get it right.

From your DotNetNuke web.config file, copy in the sections for DotNetNuke which apply to connection strings, caching providers, and SqlDataProviders. The Visual Studio Unit Testing framework will automagically pick up and use the app.config settings, so when the DotNetNuke framework code looks for config settings such as caching providers and connection strings, it will take them from the app.config file instead of looking for a web.config file in the website directory.

You'll also need the 'iFinity' sectionGroup to run the supplied code. This is because the base class you are going to use needs some data on things to initialize. The easiest way to do all this is just to copy the app.config file from the downloadable code. It has all the right settings for a simple unit test application.

You may have to copy in more Provider settings depending on what providers you are accessing in the base framework. If you start poking around in the dark depths of the DotNetNuke libraries with your unit testing, don't be surprised to find you need to reference a provider that currently isn't in your app.config file. This will probably show up as a series of null reference exceptions or something similarly hard to find. The example provided covers most of them, but for people pushing the envelope or using custom providers, you might strike trouble.

Step 4: Create a new Unit Test

You're now ready to create a DotNetNuke unit test. Create a new unit test class and give it a name (and namespace).

In your UnitTest class file, you'll need to add in references to the correct libraries, so reference these with the 'Imports' (VB) or 'using' (C#) command.

  • DotNetNuke.Data
  • iFinity.DNN.Utilities.Testing
  • YourCompany.YourModule (or whichever namespaces your SqlDataProvider and *Info classes are)

Then change the declaration of the class so that it inherits from the DnnUnitTest type. The base class hooks up all the DotNetNuke handling in the background so you don't have to.

C#
//C#
public class MyDotNetNukeClassTest : DnnUnitTest
VB
VB
Public Class MyDotNetNukeClassTest Inherits DnnUnitTest

There is a mandatory constructor on the underlying base class, so you'll need to create that. It requires a portal ID, so I normally just hardcode in whatever portalID I have on my test database I am using. You can always change it if you want to try another portal, or you can come up with a fancy solution to substitute in the portal ID if you need.

C#
//C#
public MyDotNetNukeClassTest ():base(1) //send PortalId to base class
{}
VB
VB
Public Sub New()
  MyBase.New(1) 'Send Portal ID to base class
End Sub

Step 6: Create a new test

Now create a unit test in your code, using the [TestMethod] notation as you normally would. Then simply write your unit test of your DotNetNuke code as you normally would for any other Unit Test. I normally just create my SqlDataProvider object directly in code and test it, rather than going through the indirect layer of creating the DataProvider class and using the Instance() call.

A sample test method is:

C#
//C#
[TestMethod]
public void MyDotNetNukeSqlTest()
{
    SqlDataProvider sqlProvider = new SqlDataProvder();
    MyThingInfo myThing  = new MyThingInfo();
    myThing.Property1  = "property1";
    myThing.Property2 = "property2";
    sqlProvider.SaveMyThing(myThing);
    copyOfmyThing = sqlProvider.GetMyThing();
    Assert.AreEqual(myThing.Property1, copyOfMyThing.Property1);
    Assert.AreEqual(myThing.Property2, copyOfMyThing.Property2);
}
VB
VB
<TestMethod>()
Public Sub MyDotNetNukeSqlTest()
    Dim sqlProvider as SqlDataProvider = new SqlDataProvder()
    Dim myThing as MyThingInfo  = new MyThingInfo()
    myThing.Property1  = "property1"
    myThing.Property2 = "property2"
    sqlProvider.SaveMyThing(myThing)
    copyOfmyThing = sqlProvider.GetMyThing()
    Assert.AreEqual(myThing.Property1, copyOfMyThing.Property1)
    Assert.AreEqual(myThing.Property2, copyOfMyThing.Property2)
End Sub

As you can see, my example test method simply saves a MyThing and then reads it back again, then compares the two properties to make sure they have the same value. You'll need to think about what aspects of your PA module code you'd like to test and write the unit test appropriately.

If you're lucky and have done everything right, you should just be able to run the unit test and get rewarded with a green tick. If not, well a bit of troubleshooting will be required to rid yourself of those pesky red crosses. You should be able to step right into your code and see where it is going wrong.

Troubleshooting

Screenshot - Failed Unit Tests

When things don't go so well, the dreaded red crosses

A common problem is not having your namespaces/assembly names right - it helps to ensure that you have your SqlDataProvider namespace and assembly name correct before trying to start unit testing. DotNetNuke uses a Reflection assembly that makes certain assumptions about naming standards so it's important to stick to these standards or spend hours picking through code working out why your data provider won't instantiate. You need to make sure your assembly name and namespace for the SqlDataProvider match up. An example for one of my projects is below:

  • Full type name: iFinity.DotNetNuke.Modules.Directory.Data.SqlDataProvider
  • Assembly Name: iFinity.DotNetNuke.Modules.Directory.SqlDataProvider.dll

This will be referenced in your DataProvider module, most likely.

If you don't have all of the DotNetNuke libraries referenced properly in your test project, you're going to end up with errors like this one:

Unable to create instance of class iFinity.Tagger.Test.TagInfoSQLTest. Error: 
System.TypeInitializationException: The type initializer 
for 'DotNetNuke.Services.Cache.CachingProvider' 
threw an exception. ---> System.ArgumentNullException: Value cannot be null.

Parameter name: type.

This particular error is because the Caching provider is referenced in the app.config file but not in the project references. In fact, you'll be lucky if you can get one this clear to read.

When trying to get this to work, it's a good idea to turn on exception catching at the point it is thrown, so that you can capture exceptions right where they happen. Otherwise you'll end up with a lot of 'null reference exceptions' as objects fail to instantiate properly. You'll need to closely inspect the stack trace and exception stack - often the problem will be a not-found file deep down in the call chain, and takes persistence and detail-oriented work to get right.

Remember to set exception handling back to 'unhandled only' once you've solved your problem, or you will end up chasing down a lot of dark alleys where code has been written with the intention of using a try..catch block to handle normal operation (which is a performance no-no, but sometimes gets used). There is a bit of this in DotNetNuke, particularly in the CBO namespace (which is also why it should be avoided and replaced with directly-cast database routines, IMO).

Writing all of the required Unit Tests

Once you have got one unit test working, it's time to write the rest of them. Depending on what pattern you follow, you're likely to have at least four operations that need testing (the CRUD operations). Assuming a module called 'MyThings' with a MyThingInfo class, your DataProvider is going to have a list of methods that look something like this:

  • AddMyThing
  • UpdateMyThing
  • GetMyThing
  • DeleteMyThing

There should be a unit test written for each one of these (AddMyThingTest, UpdateMyThingTest, etc.). But don't forget unit tests are there for testing, so you'll need to use the GetMyThing call to get back your record after you save it. And then you should check that all of the properties have been returned properly. You also should test for things like adding in duplicate keys; what happens when you pass invalid data - not just the 'positive' test cases. And don't forget to delete your test data at the end of the test, unless you want your database clogged up with test data.

Of course, in practice I'm yet to create a DotNetNuke module with anything like the simplicity of this, but you never know, but it's good to illustrate the concept. The module I'm currently working on has about 25 different data access methods, each of which has one or two unit tests written against it.

How does it work?

The underlying difficulty with mixing unit testing and DotNetNuke, as mentioned earlier, was the assumption that the code will be running inside a web application. The real tricky bits come in where the HttpContext is used to cache items, as well as the various DotNetNuke caching providers. Now the DotNetNuke code is robust enough to create objects when they aren't found in the cache and guarantee you an instantiated object when you ask for one, but it does expect the cache to be there in the first place. Without a web app, you've got not HttpContext, and you're stuck.

Are you mocking me?

Mocking is the process of mocking backend functionality that is not present when unit testing. Also called 'stubs' and other names, it basically means providing something for the code to call when it is running in the Test context rather than the full DotNetNuke app. It's not going to be long before you make a call to something like HttpContext and find that the Unit testing context isn't running such an animal. So you need to mock some of the things that the DotNetNuke takes for granted. We can get into a discussion of design architectures and separation of libraries vs. websites at another time. I never criticise the framework because it's done voluntarily and is distributed free.

To see a simple way of hacking up a mock HTTP Context, look at the following: Simulating HTTP Context For Unit Tests Without Using Cassini or IIS

A more thorough approach using Cassini (heavier but more complete) is NUnit Unit Testing of ASP.NET Pages, Base Classes, Controls and other widgetry using Cassini (ASP.NET Web Matrix/Visual Studio Web Developer).

I've implemented a version of the method used in the first link. This provides the DotNetNuke library with the right objects for it to run.

For those too lazy to RTA, what it does is set up a simple object that mocks some of the HttpContext functions. Not all, so you won't have to push it very far to break it (it won't support the vast majority of calls to HttpContext). But you're building class libraries! What are you doing assuming there will be an HTTP Context anyway?

Where can I put my stuff?

DotNetNuke heavily uses caching to store all sorts of things during runtime. Some of these are the objects for the providers (depending on whether you use disk caching or not). And without somewhere to stash all those instantiated providers, you're going to get exceptions all over the place. So you need to make sure you have your Cache paths set up. No, there isn't a magic 'SetMyCachePathAndMakeMyProblemsGoAway' method you can call. You have to craft up a way to trick the DotNetNuke library into thinking it's actually running as normal. To do this, a few 'startup' variables have to be set.

In my base class, these paths get set to values which are specified in the app.config file. I don't really like this approach much myself, because it means that any other machine running your unit tests (a build machine, for instance) needs to have the same paths specified. I didn't have much luck getting relative paths to work well, although I'm willing to concede it's probably not that hard.

If you look at the iFinity/DnnUnitTest settings, you'll see a couple of paths specified:

XML
<DnnUnitTest hostMapPath="C:\DotNetNuke\source\DotNetNuke451\Website\portals\1\" 
  appPath="C:\DotNetNuke\source\DotNetNuke451\Website\" appName="DotNetNuke" 
  simulatedServer="WOMBAT" simulatedPage="default.aspx"/> 

These are:

  • hostMapPath: This is where cached data etc., gets placed when the application is running normally. In this case, I have the path set to where my local DotNetNuke install exists.
  • appPath: The root path of where the website normally runs. Again, I have this set to my local install.
  • simulatedServer: As far as I can tell, this value doesn't mean a hell of a lot, but it needs to be specified. I use my local machine name; "localhost" would probably work as well.
  • simulatedPage: The name of the ASP.NET page you are simulating. Because all DotNetNuke pages run as default.aspx, this one is pretty easy.

Note: You don't have to set these to the same value as a local DotNetNuke install, and you don't really have to have a whole DotNetNuke website installed on the computer you are using. As long as they are a valid path, everything will be fine. I just use my local DotNetNuke install because the folders are already created with the correct permissions.

How far can this be pushed?

Eventually, you'll end up with either half the DotNetNuke website loaded up trying to unit test, or you'll find yourself trying frantically to mock something that can't and won't be mocked. However, with some creative coding and delving into the core source code, you'll find that most problems can be overcome. Then you'll have a reliable set of unit tests that can be run every time you make a change to the code. Any breakages will quickly be found and you'll feel confident in doing some real refactoring when circumstances require it.

Not only that, but by the time you get around to writing your UI code in your ASCX file, you'll be supremely confident that your data layer code works exactly as you intended. It's a major pain to debug web applications, so debugging it before you hook it into a web app is much easier.

Did it work?

The code started out as a helper I continually refined until it did what I wanted, and this article started out as my 'cheat sheet' to help me troubleshoot faster. It has finally morphed into something for public consumption, but there's probably cryptic instructions and problems with it that I haven't picked up. If you use it and have trouble using it or don't understand it, use the comments field below to ask questions and I'll attempt to clear up any difficulties with it.

* Whilst studying at university, I briefly shared a house with a Scottish body builder who worked as a bouncer. He ate tuna and pineapple for lunch every day as it was high in protein and sugars and low in fat. Oh, and it cost about $1 for the two cans. It's the mixture of salty and sweet tastes, dry and juicy textures that sets it off. Far better than noodles (a.k.a. Ramen) because you don't have to cook it, and with more nutritional value.

History

  • Initial version: 8 June 2007
  • Updated with Data Driven article links: 18 June 2007

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Product Manager DNN Corp
Australia Australia
Bruce Chapman is the Product Manager for Cloud Services at DNN. He’s been an active member of the DNN Community since 2006 as a contributor, vendor and now employee of DNN Corp.

You can read his blog at http://dnnsoftware.com/blog or follow him on Twitter @brucerchapman

Comments and Discussions

 
GeneralThat is exactly what I need but I cna not pass the error - please help Pin
stardv9-Nov-09 13:55
stardv9-Nov-09 13:55 
GeneralRe: That is exactly what I need but I cna not pass the error - please help Pin
JasenF22-Mar-10 5:49
professionalJasenF22-Mar-10 5:49 
GeneralSuperb article, but I'm stuck with an error Pin
Eduardas21-Apr-08 9:35
Eduardas21-Apr-08 9:35 
GeneralRe: Superb article, but I'm stuck with an error Pin
Bruce Chapman DNN21-Apr-08 11:18
professionalBruce Chapman DNN21-Apr-08 11:18 
GeneralRe: Superb article, but I'm stuck with an error Pin
Eduardas22-Apr-08 11:30
Eduardas22-Apr-08 11:30 
GeneralRe: Superb article, but I'm stuck with an error Pin
Bruce Chapman DNN22-Apr-08 14:23
professionalBruce Chapman DNN22-Apr-08 14:23 
GeneralRe: Superb article, but I'm stuck with an error Pin
richard9622-Apr-08 18:41
richard9622-Apr-08 18:41 
GeneralRe: Superb article, but I'm stuck with an error Pin
Bruce Chapman DNN22-Apr-08 20:51
professionalBruce Chapman DNN22-Apr-08 20:51 
GeneralRe: Superb article, but I'm stuck with an error Pin
richard961-May-08 17:33
richard961-May-08 17:33 
GeneralRe: Superb article, but I'm stuck with an error Pin
CBoland27-Aug-08 12:37
CBoland27-Aug-08 12:37 
Generalsession state [modified] Pin
jbonnie27-Jun-07 11:19
jbonnie27-Jun-07 11:19 
QuestionPA Setup Pin
kwgainey21-Jun-07 17:09
kwgainey21-Jun-07 17:09 
AnswerRe: PA Setup Pin
Bruce Chapman DNN21-Jun-07 17:33
professionalBruce Chapman DNN21-Jun-07 17:33 
GeneralVery Good Pin
dawnstar_4316-Jun-07 22:04
dawnstar_4316-Jun-07 22:04 
GeneralUnit testing on the Help Project Pin
philipbeadle10-Jun-07 0:13
philipbeadle10-Jun-07 0:13 
GeneralRe: Unit testing on the Help Project Pin
Bruce Chapman DNN18-Jun-07 20:31
professionalBruce Chapman DNN18-Jun-07 20:31 
GeneralHttpContext in DotNetNuke Pin
Joe Brinkman8-Jun-07 4:18
Joe Brinkman8-Jun-07 4:18 
GeneralRe: HttpContext in DotNetNuke Pin
Bruce Chapman DNN18-Jun-07 20:27
professionalBruce Chapman DNN18-Jun-07 20:27 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.