Tyler Treat
Tyler Treat

Reputation: 15008

Problem using Moq in unit tests

I've been exploring the use of mock objects in unit testing and have been trying out the Moq framework for .NET. I'm having some issues in attempting to test a service-layer method that returns a domain object from a database.

Here is my setup:

    [SetUp]
    public void DoSetupTasks()
    {
        mockDao = new Mock<IHibernateDao>();
        _hibernateService = new HibernateService(mockDao.Object);
        mockDomainObject = new Mock<DomainBase>();
        dmBase = new DomainBase()
        {
            Id = 5
        };
    }

Here is the unit test I am having problems with. The method FindById() returns a DomainBase object based on a given ID and Type.

    [Test]
    public void TestFindById()
    {
        mockDomainObject.Setup(dmb => dmb.Id.Equals(It.IsAny<long>())).Returns(true);
        mockDao.Setup(dao => dao.FindById(
            It.IsAny<long>(),
            It.IsAny<Type>()
        )).Returns(mockDomainObject.Object);

        _hibernateService.FindById(dmb.Id, typeof(DomainBase));
        mockDomainObject.VerifySet(dmb => dmb.Id = dmBase.Id);
    }

When I run the unit test, it throws the following exception:

Exception: Invalid setup on a non-virtual (overridable in VB) member: dmb => dmb.Id.Equals(It.IsAny<Int64>())

I'll admit, I'm pretty unfamiliar with the framework. I've been trying to follow some tutorials on it, but I haven't been able to get it figured out.

Upvotes: 2

Views: 7255

Answers (1)

Dylan Beattie
Dylan Beattie

Reputation: 54160

Try something more like this:

[Test]
public void TestFindById() {

    const int TEST_ID = 5;
    // Configure your mock DAO to return a real DomainBase 
    // when FindById is called.
    mockDao
        .Setup(dao => dao.FindById(TEST_ID, It.IsAny<Type>())
        .Returns(new DomainBase() { Id = TEST_ID });

    var domainObject = _hibernateService.FindById(TEST_ID , typeof(DomainBase));

    // Make sure the returned object is a DomainBase with id == TEST_ID 

    Assert.IsEqual(TEST_ID , domainObject.Id);
    Assert.IsInstanceOf<DomainBase>(domainObject);

    // and verify that your mock DAO was called with the same argument passed to 
    // your NHibernate service wrapper:
    mockDao.VerifyAll();
}

You shouldn't need to mock your domain objects - you're generally better off mocking your data layer and services and then passing real domain objects (containing sample/test data) between your logic code under test and your mocked service layers.

EDIT: To test save and delete methods, you'll want to do something like this. Note how we're using the Callback() method of the Moq library to run some code when the mock method is called, and within this callback we're asserting that the object passed to our method was the object we were expecting:

[Test]
public void TestSaveDomainBase() {

    const int OBJECT_ID = 5;

    mockDao
      .Setup(dao => dao.Save(It.IsAny<DomainBase>()))
      .Callback((DomainBase d) => {
         // Make sure the object passed to Delete() was the correct one
         Assert.AreEqual(OBJECT_ID, d.ID);
      });

    var objectToSave = new DomainObject() { Id = OBJECT_ID };

    _hibernateService.Save(objectToDelete);

    mockDao.VerifyAll();
}


[Test]
public void TestDeleteDomainBase() {

    const int OBJECT_ID_TO_DELETE = 5;

    mockDao
      .Setup(dao => dao.Delete(It.IsAny<DomainBase>()))
      .Callback((DomainBase d) => {
         // Make sure the object passed to Delete() was the correct one
         Assert.AreEqual(OBJECT_ID_TO_DELETE, d.ID);
      });

    var objectToDelete = new DomainObject() { Id = OBJECT_ID_TO_DELETE };

    _hibernateService.Delete(objectToDelete);


    mockDao.VerifyAll();
}

Upvotes: 6

Related Questions