Mock ADO.NET with ease using IDataReader Stub objects

July 10, 2007 11:16 by ndibek

 

Before I start, I would like to point outthat if you are confused about differences between Mock and Stub objects, please readthe Fowler’s post on the subject:

http://www.martinfowler.com/articles/mocksArentStubs.html

I have seen too many “Unit tests” where developersdo not isolate the test to the unit (object/method) being tested.  Yesthey are testing a business object, but to test its behavior they load half of theobject hierarchy in the project.  Moreoverif your object gets initialized from external resource like a database, then theycreate this complex “test databases” that contain their test data.  Thesedatabases have to be shared with other developers.  Ifyou have a continuous build environment that runs tests as a part of the build process,then you have to have a copy of the test database there.  Allthese test databases have to be modified as you modify schema/data in your developmentdatabase.  In addition if your test modifiesdata in the database then you need to setup an additional process in the SetUp orTeardown to restore the data to initial state.

So the test should be simpler if we mock thedb dependency, right?  So what does thatinvolve?  Let’s say we have an objectcalled project Project with constructor as described below:

public Project(IProjectGateway gateway, int id)

{

    using(IDataReader dr= gateway.GetProjectBy(id)) {

        if(dr.Read()){

            _id= dr.GetInt32(0);

            _name= dr.GetString(1);

            _date= dr.GetDateTime(2);                   

        }

    }

}

 

Where IProjectGateway is an interface defininga set of methods/and object in charge of persistence of the Project data to and fromthe database.    Solets look how that interface might look like:

public interface IProjectGateway {

    IDataReader GetProjectBy(int id);

    ...

}

So far this is simple, right?Actual implementation of that interface does not matter for our test.  Why?  Becausewe are not testing database, its resources/file storage, hardware, connection pooling,network connections, etc.  All we aresupposed to test is that our object properly populates its field from a returned DataReader.  

To test the Project object without the databaseroundtrip we will only need to mock the IProjectGateway, setting the expectationsfor the GetProjectBy(id) to return our test data (data reader).  I have to mentionthat the dynamic mocks in the examples bellow were done using my favorite mockingtool TypeMock.Net.

[Test]

public void AssureFetchMapsFields()

{

    Mock<IProjectGateway>projGatewayMock = MockManager.Mock<IProjectGateway>();

 

    projGatewayMock.ExpectAndReturn("GetProjectBy", new ProjectDataStub().CreateDataReader());

   

    Project project= new Project(projGatewayMock.MockedInstance,1);

 

    Assert.AreEqual(1,project.Id);

    Assert.AreEqual("Test",project.Name);

    Assert.AreEqual(DateTime.Parse("1/1/2000"),project.Date);

}

 

So lets see what exactly have we done in thetest above.  First line creates our dynamicmock instance.  Second line is the interestingpart.  It states that we expect one methodcall on our IProjectGatewayMock, and that is “GetProjectBy()” method.  Onceit is called we want the IDataReader to be returned from ProjectDataStub.CreateDataReader().  Therest of the code instantiates the Project object and then assures that the Project’sproperties are initialized.

But what about this ProjectDataStub.CreateDataReader()?  WellI have noticed the ability of the DataTable objects to create an instance of TableDataReader(whichimplements IDataReader).  So theoreticallywe could create a DataTable with column types that reflect the types of the actualcolumns of the table/view we are fetching from db and populate this DataTable withtest record(s).  Then our mock IProjectGatewaycan return the IDataReader from this object, and voila – no db or any other externalconnection used in the test.

So if we follow this logic the ProjectDataStubshould be an object that inherits from DataTable and populates its columns and rowswith test records when it is constructed.  Butcoding these stub object for each and every test might be a bit tedious.  Tosolve this problem I have created a rather simple tool that allows us to generatethis DataStub simply by copying and pasting select SQL statements from the fetch StoredProcedure and executing it.

 

Above is the scren shot showing how tool works and its output.  If you thinkthat this tool might be useful for you feel free to download the code from the linkbellow:

StubGenerator.zip(780.62 KB)

Is this all when it comes to testing the DAL?  No, obviously we have only testedthe data mapping part.  Code in this Project constructor can throw SqlExcpetion(database down, network problem, schema problem).  We need to assure that ourcode handles that.

Now putting the try/ctach block in the Project constructor does not make sense - Ido not want Project instantiated if we were unable to retreive its data.  Sohow do we test this case?  Lets assume that our application is using Model ViewPresenter architecture, and that the object instantiating our Project is the Presenterobject of the View that displays Project.  Obviously we need to assure that thisPresenter can recover from the SqlException thrown by Project.  Lets take a lookat the code bellow:

public class ProjectPresenter

{

....

 

public void DisplayProject(){

    try{

        Projectp = new Project();

 

        ...do something with project like display it on the view etc...

 

    }catch(SqlExceptione){

        Log.LoqException(e);      

    }

}

 

So if we can assure that the Log.LogException(e) is called when the Project throwsthe SqlException, that would be a proof that the exception was handled. Keep in mind that in Test Driven Development we would be writting the test prior tothe DisplayProject() method being written (and the catch block inside of it existing)

Dynamic mocking helps us here also.  In this case we are testing the Presenter,which means that the other two objects Log and Project would be mocked.  Testwould encompass mocking ProjectConstructor instead of returning Project throws SqlException,and then assuring that the Log Mock accepts the call to LogException(e).

In addition there would be tests that we need to run for Insert, Update and Delete(if Project needs to support that), but I will leave that for a future post.

 

 

Digg It!DZone It!StumbleUponTechnoratiRedditDel.icio.usNewsVineFurlBlinkList

Currently rated 5.0 by 1 people

  • Currently 5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Comments