Archive

Archive for the ‘Unit Testing’ Category

Refactoring those static method calls for testability!

May 27th, 2011 No comments

One thing I love about working on a legacy application is the weird things I get to problem solve.

A lot of the old system has an awful lot of static method calls, which doesn’t leave much to be desired for when it comes to unit testing. I was working on a piece of the system today which is written in .NET 2.0, and is tightly coupled to everything in existence.

I didn’t want to go introduce interfaces and wrapper classes in order to abstract out all the dependencies, but I wanted to unit test the work without having to touch the database, which is what these static method calls were doing.

The code I dealt with was something along the lines of…

public class ProductService
{
    public static void Save(Product product)
    {
        throw new Exception("This would normally touch a db…");
    }
}

(Example is completely made up for this blog post and isn’t actual code from work)

The class that calls this is along the lines of…

public class SomeService
{
    public void DoSomething(User user, Product product)
    {
        //Do a bunch of stuff…
        if (user.IsVip)
            product.Price *= 0.90;

        ProductService.Save(product);

        //Do some more stuff…
    }
}

So there’s a direct call to the static method ‘Save’, now unit testing this class, like…

[TestMethod]
public void Test()
{
    //Arrange
    SomeService serviceUnderTest = new SomeService();

    User user = new User();
    user.IsVip = true;

    Product product = new Product();
    product.Price = 5.00d;

    //Act
    serviceUnderTest.DoSomething(user, product);

    //Assert
    Assert.AreEqual(4.5d, product.Price);
}

Running the test I would like to expect a result of ‘4.5’ for the price, but what I get is the exception thrown from the ProductService.

The solution, that didn’t involve interfaces, a wrapper around the service, etc.

public class SomeService
{
    internal delegate void ProductServiceDelegate (Product product);

    private ProductServiceDelegate _productServiceDelegate;
    internal ProductServiceDelegate ProductServiceSave
    {
        get
        {
            if (_productServiceDelegate == null)
                _productServiceDelegate = ProductService.Save;

            return _productServiceDelegate;
        }   
        set { _productServiceDelegate = value; }
    }

    public void DoSomething(User user, Product product)
    {
        //Do a bunch of stuff…
        if (user.IsVip)
            product.Price *= 0.90;

        ProductServiceSave(product);

        //Do some more stuff…
    }
}

So what I’ve done is introduce a delegate for the ProductService’s ‘Save’ method, and made it internal with ‘InternalsVisibleTo’ on the AssemblyInfo file so that unit testing project can see it.

Now I can update the unit test with:

[TestMethod]
public void TestMethod1()
{
    SomeService serviceUnderTest = new SomeService();

    User user = new User();
    user.IsVip = true;

    Product product = new Product();
    product.Price = 5.00d;

    serviceUnderTest.ProductServiceSave =
        new SomeService.ProductServiceDelegate(
            delegate(Product productParam)
                {
                    productParam.Id = 1;
                });

    serviceUnderTest.DoSomething(user, product);

    Assert.AreEqual(4.5d, product.Price);
    Assert.AreEqual(1, product.Id);
}

So on the class under test I add a test method to the property which just sets the Id of the product to ‘1’, to simulate that the product was saved, and now I don’t have to worry about the dependency on the ProductService class anymore.

System.Data.SQLite isolationLevel Exception

January 2nd, 2011 Comments off

I introduced SQLite to our Unit Testing at work to aid with testing the stuff written with NHibernate, most of our repositories are rather simple but some of them require some specific criteria that it would be nice to test our queries work.

The problem is some of the queries have a transaction with the IsolationLevel as ReadUncommitted.

Everything works perfectly fine until it comes to testing, the problem is SQLite does not support anything other than Serializable and ReadCommitted.

I spent a while trying to see if there was a way to have an interceptor for NHibernate to capture the BeginRequest and replace the isolation level with something that would work, when that failed I took a look at extending the SQLite dialect but that just became confusing.

In the end I reflected the System.Data.SQLite assembly (before i downloaded the sourcecode) to see what was happening when BeginTransaction was being called.

There’s two places it checks, the first is in SQLiteConnection under BeginTransaction:

if (isolationLevel != IsolationLevel.Serializable && isolationLevel != IsolationLevel.ReadCommitted)
    throw new ArgumentException("isolationLevel");

The second is in the same file under Open:

if (_defaultIsolation != IsolationLevel.Serializable && _defaultIsolation != IsolationLevel.ReadCommitted)
    throw new NotSupportedException("Invalid Default IsolationLevel specified");

The second one is only if you’ve configured SQLite to have a default isolationLevel, so I’ve changed both.

So before the change, if i ran the following code (to demonstrate the scenario):

[TestMethod]
public void SQLite_WithTransction_ShouldNotThowException()
{
    object id;

    using (var tx = _session.BeginTransaction(IsolationLevel.ReadUncommitted))
    {
        id = _session.Save(new Test
        {
            Name = "Test :) "
        },);

        tx.Commit();
    }

    Assert.AreEqual(1, Convert.ToInt32(id));
}

When the test runs, the following error is thrown:

NHibernate.TransactionException: Begin failed with SQL exception —> System.ArgumentException: isolationLevel

Now if I update the two exceptions:

if (_defaultIsolation != IsolationLevel.Serializable && _defaultIsolation != IsolationLevel.ReadCommitted)
    _defaultIsolation = IsolationLevel.ReadCommitted;

And

if (isolationLevel != IsolationLevel.Serializable && isolationLevel != IsolationLevel.ReadCommitted)
    isolationLevel = _defaultIsolation;

Compile and all that stuff…

Now when I run the test:

image

Problem solved, now I can run unit tests against repositories that happen to use Isolation Levels not supported by SQLite.

Attached is the assembly if anyone else wants to use it for their tests.

Categories: NHibernate, Unit Testing Tags:

Moq–Using Params in the Returns

December 6th, 2010 Comments off

Had an interesting scenario to solve today, while I was away a bunch of unit tests got turned off since they broke during some refactoring, I spent the day fixing theses unit tests.

The method under test had a dependency on another class, it performed two actions on this class.

  1. to get a list of data
  2. to return a DateRange (this is done in a foreach loop)

 

DateRange is just a class with a start/end property of DateTime. The method is sort of like this (obviously with proper names I just wrote some random code to illustrate the scenario)

  1. public IEnumerable<Stuff> GetStuff(DateTime dateList)
  2. {
  3.     var result = new List<Stuff>();
  4.     var someService = IoC.Resolve<ISomeService>();
  5.     var data = someService.GetData();
  6.     foreach (var date in dateList)
  7.     {
  8.         var tempDate = date;
  9.         DateRange range = someService.GetDates(OpenRules, tempDate);
  10.         //do stuff…
  11.     }
  12.     return result;
  13. }

So the same service is called twice, the issue is when iterating over the dateList, it needs to be filtered based on a DateRange during the day. So say, 8am till 10pm. Removing the stuff outside of that time period during that day.

The method GetDates would take some rules and based on the date, create the the DateRange for the filter.

To test this method we needed to get some data, and filter it,and check the list had the correct data after being filtered.

We mocked ISomeService,using Moq:

  1.     var mockService = new Mock<ISomeService>();
  2.     mockService.Setup(x => x.GetData()).Returns(FakeData());
  3.     IoC.Register<ISomeService>(mockService.Object);

The issue is now in the foreach, I couldn’t just mock a return because it would be the same value and I wouldn’t be testing the actual foreach. I ended up writing something along the lines of:

  1. mockService.Setup(x => x.GetDates(It.IsAny<OpenCloseRule>(), It.IsAny<DateTime>()))
  2.             .Returns(
  3.                 (OpenCloseRule openRules, DateTime, startDate) =>
  4.                 //Some logic
  5.                 return new DateRange(start, end);
  6.             );

This worked great, except, if the implementation of GetDates changes, would result in false positives or false negatives, or something.

So I ended up changing it to:

  1. mockService.Setup(x => x.GetDates(It.IsAny<OpenCloseRule>(), It.IsAny<DateTime>()))
  2.             .Returns(
  3.                 (OpenCloseRule openRules, DateTime, startDate) =>
  4.                 return (new SomeService()).GetDates(openRules, startDate);
  5.             );

That way if the implantation changes, the test will break or, work. Personally I don’t like this cos I’m testing two things instead of one. But at least I can test the original method works correctly.

So what I actually learnt was that the returns method on the mock can actually use the parameters that were passed in. That’s pretty awesome. Bed time, it’s late.

Categories: Moq, Unit Testing Tags: