Thursday, 28 February 2013

Checking Parameter Passed to a Method with Moq

Say we are using TDD and would like to add more business logic to the method below. We would like to make sure that the object's created and updated time should be set with current time.
public void Add(Item item)
{        
    // set item created time

    // set item updated time 

    repository.Add(item);
}

Moq provides some argument checking methods that we can use; there are:
- It.Is<Type>(...) ,
- It.IsInRange<Type>(...) and
- It.IsRegex(...)

Below is a unit test using one of the methods; It.Is<Type>(o => checking_condition)
[Fact]
public void Created_time_should_be_set_as_current_time()
{
    // Arrange
    var item = new Item(); 
    var timeToCompare = DateTime.Now.AddHours(-1);

    // Act
    itemDS.Add(item);

    // Assert
    repository.Verify(c => c.Add(It.Is<Item>(i => i.CreatedAt.CompareTo(timeToCompare) > 0)));
}
or we can write like this:
[Fact]
public void Created_time_should_be_set_as_current_time()
{
    // Arrange
    var item = new Item(); 
    var timeToCompare = DateTime.Now.AddHours(-1);
    repository.Setup(c => c.Add(It.Is<Item>(i => i.CreatedAt.CompareTo(timeToCompare) > 0)));

    // Act
    itemDS.Add(item);

    // Assert
    repository.VerifyAll();
}
Run the test. It will fail. Then we can put the code item.CreatedAt = DateTime.Now; on the method. Run the test again. It should pass now.

Now we want to create a test to make sure updated time is set. We will use another way to check the parameter passed to a method. We will use Callback feature to retrieve the parameter. We cannot put a checking conditional logic in Callback argument. However we can assign the passed parameter to an existing object or add it to an existing collection then later we can inspect it.

[Fact]
public void Updated_time_should_be_set_as_current_time()
{
    // Arrange
    var item = new Item(); 
    var timeToCompare = DateTime.Now.AddHours(-1);

    // repository.Setup(c => c.Add(It.Is<Item>(i => i.UpdatedAt.CompareTo(timeToCompare) > 0)));
    // alternate way by using Callback
    repository.Setup(c => c.Add(It.IsAny<Item>())).Callback<Item>(i => item = i);

    // Act
    itemDS.Add(item);

    // Assert
    // repository.VerifyAll();
    // now we are using Callback
    Assert.True(item.UpdatedAt.CompareTo(timeToCompare) > 0);
}

Just for a note, if required, Callback can be combined with Returns function as well. For example:
mock.Setup(. . .)
    .Returns(. . .)
    .Callback(. . .)

mock.Setup(. . .)
    .Callback(. . .)
    .Returns(. . .)
    .Callback(. . .)

Run the test. It will fail. Put the code item.UpdatedAt = DateTime.Now; Now the test will pass.

Our updated method now is
public void Add(Item item)
{        
    // set item created time
    item.CreatedAt = DateTime.Now;

    // set item updated time 
    item.UpdatedAt = DateTime.Now;

    repository.Add(item);
}

References:
http://code.google.com/p/moq/wiki/QuickStart
http://stackoverflow.com/questions/3269717/moq-how-to-get-to-a-parameter-passed-to-a-method-of-a-mocked-service

No comments: