TypeMock Isolator and LINQ

I’m doing some work with TypeMock and I start to love it more and more. For example, how about removing your database completely from your continuous tests? Sure you can do some database testing during the nightly build, but please keep the tests as fast as possible.

A little story about a build
tableJust a few days ago on a Friday my colleague Alex Thissen and I were planning to leave the building. Unfortunately after checking in some code, a few unit tests failed in Alex’s project. It were some really minor changes, but as it was 20:00 hours already, we weren’t as sharp anymore. After 4 trial-and-error sessions getting the test to work, we could go home. The code changes took only a few seconds, running the build took much, much longer. Mainly because Visual Studio 2008 for DB Professionals was deploying the database every time. What if we only needed that during the nightly build and actually had the continuous build running much faster?

Imagine a nice little table like the one on the right. Just some Class-A trainers in there. Imagine some code retrieving the trainers.

public List<MyTrainer> GetTrainers()

{

  List<MyTrainer> trainers = new List<MyTrainer>();

 

  using (SqlConnection con = new SqlConnection())

  {

    con.ConnectionString = @"Server=.sqlexpress;database=courses;integrated security=sspi;";

    con.Open();

 

    SqlCommand cmd = new SqlCommand();

    cmd.Connection = con;

    cmd.CommandText = "select * from trainers";

 

    SqlDataReader rdr = cmd.ExecuteReader();

    while (rdr.Read())

    {

      MyTrainer t = new MyTrainer();

      t.Name = rdr["Name"].ToString();

      trainers.Add(t);

    }

  }

 

  return trainers;

}

Normal ADO.NET code for retrieving trainers and adding them to a collection of MyTrainer objects. We could just test this with a special test database so that we always get the same results. We could create a very easy test where we just execute the GetTrainers method and see what data it returns. But as this accesses the database you might want to do this in a nightly batch. To be a little more sure you don’t break the build at night, you can always run these tests from your local machine.

[TestMethod]

public void GetTrainers_DBAccessed_Return7Trainers()

{

  DAL dal = new DAL();

  List<MyTrainer> trainers = dal.GetTrainers();

 

  Assert.IsNotNull(trainers);

  Assert.AreEqual(7, trainers.Count());

  Assert.AreEqual("Anko Duizer", trainers[0].Name);

}

But we really want to test the code without actually connecting to the database. You could stub out a lot, but that’s a lot of work. Especially because the datareader is very hard to stub out. I’ve once used a datareader that was running on a dataset with one ore more tables in it. Pretty nice, but still took a whole lot of code to fill the datareader initially.

When we want to use a mocking framework like TypeMock, it still requires some code. We need to actually mock out three objects and create expectations for them. Here’s the code.

[TestMethod, VerifyMocks]

public void GetTrainers_NoDBAccess_Return2Trainers()

{

  using (RecordExpectations rec = RecorderManager.StartRecording())

  {

    using (SqlConnection con = new SqlConnection())

    {

      con.ConnectionString = "";

      con.Open();

 

      SqlCommand cmd = new SqlCommand();

      cmd.Connection = con;

      cmd.CommandText = "";

 

      SqlDataReader fakeReader = RecorderManager.CreateMockedObject<SqlDataReader>();

      rec.ExpectAndReturn(cmd.ExecuteReader(), fakeReader);

      rec.ExpectAndReturn(fakeReader.Read(), true).Repeat(2);

      rec.ExpectAndReturn(fakeReader["name"].ToString(), "Dennis van der Stelt");

      rec.ExpectAndReturn(fakeReader["name"].ToString(), "Alex Thissen");

    }

  }

 

  DAL dal = new DAL();

  List<MyTrainer> trainers = dal.GetTrainers();

 

  Assert.AreEqual("Dennis van der Stelt", trainers[0].Name);

  Assert.AreEqual("Alex Thissen", trainers[1].Name);

}

As you can see we set up a recorder and created expectations for the values that should be returned. The reader.Read method should return true twice so we can fill two MyTrainer objects with data. We then set expectations for the two times the name column is accessed using the reader. When finished recording, we actually execute the code we’re testing and mocking. Of course this is a very simple implementation, but it’s to show how to mock things out. For more advanced scenarios you should check the TypeMock manual.

Now let’s see how we would retrieve the same data with LINQ.

public List<Trainer> GetTrainersViaLINQ()

{

  CoursesDataContext db = new CoursesDataContext();

 

  var query = from t in db.Trainers

              select t;

 

  return query.ToList();

}

This is a lot less code. When we want to mock this out, we again use a recorder and just use the same code. However LINQ to SQL returns an IQueryable collection of Trainer objects. You can’t use recorder.Return(someList.AsQueryable()) inside the recording-block because then the AsQueryable would be mocked out as well. So we have to prepare the collection of trainers in advance.

[TestMethod, VerifyMocks]

public void GetTrainersViaLINQ_NoDBAccess_Return2Trainers()

{

  var fakeTrainers =

    (new [] {

      new Trainer() { Name = “Dennis van der Stelt” },

      new Trainer() { Name = “Alex Thissen” } }

    ).AsQueryable();

 

  using (RecordExpectations rec = RecorderManager.StartRecording())

  {

    CoursesDataContext db = new CoursesDataContext();

 

    var query = from t in db.Trainers

                select t;

 

    rec.Return(fakeTrainers);

  }

 

  DAL dal = new DAL();

  IEnumerable<Trainer> trainers = dal.GetTrainersViaLINQ();

 

  Assert.AreEqual(2, trainers.Count());

  Assert.AreEqual(“Dennis van der Stelt”, trainers.ElementAt(0).Name);

  Assert.AreEqual(“Alex Thissen”, trainers.ElementAt(1).Name);

}

You can see I prepared the fakeTrainers collection in advance, start recording and set the return value to the prepared collection. I then actually execute the code inside my tested class and assert that everything’s as expected.

You may also like...

Click on a tab to select how you'd like to leave your comment

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.