Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I Setup overloaded method invocations in Moq?

I'm trying to mock a mapping interface IMapper:

public interface IMapper<TFoo, TBar> {
    TBar Map(TFoo foo);
    TFoo Map(TBar bar);
}

In my test, I'm setting the mock mapper up to expect an invocation of each (around an NHibernate update operation):

//...
_mapperMock.Setup(m => m.Map(fooMock.Object)).Returns(barMock.Object);
_mapperMock.Setup(m => m.Map(barMock.Object)).Returns(fooMock.Object);
//...

However, when the second Map invocation is made, the mapper mock throws because it is only expecting a single invocation.

Watching the mapper mock during setup at runtime, I can look see the Map(TFoo foo) overload get registered, and then see it get replaced when the Map(TBar bar) overload is set up.

Is this a problem with the way Moq handles setup, or is there a different syntax I need to use in this case?

EDIT Here is the actual instantiation code from the test constructor:

public class TestClass {
    private readonly MockRepository _repository = new MockRepository(MockBehavior.Strict);

    public TestClass() {
        //...
        _mapperMock = _repository.Create
            <IMapper<RequestData.Foo, ResponseData.Bar>>();
        //...
     }
}

EDIT 2

Here is a complete failing test case:

public interface IMapper<TFoo, TBar> {
    TFoo Map(TBar bar);
    TBar Map(TFoo foo);
}

public class Foo {
    public override int GetHashCode() {
        // return base.GetHashCode();
        return 1;
    }
}

public class Bar {
    public override int GetHashCode() {
        // return base.GetHashCode();
        return 2;
    }
}

[Test]
public void TestIt()
{
    // Arrange
    var _mapperMock = new Mock<IMapper<Foo, Bar>>(MockBehavior.Strict);
    var fooMock = new Mock<Foo>();
    var barMock = new Mock<Bar>();

    _mapperMock.Setup(m => m.Map(fooMock.Object)).Returns(barMock.Object);
    _mapperMock.Setup(m => m.Map(barMock.Object)).Returns(fooMock.Object);

    // Act - breaks on first line below this comment
    var bar = _mapperMock.Object.Map(fooMock.Object);
    var foo = _mapperMock.Object.Map(barMock.Object);

    // Assert
    _mapperMock.Verify(x => x.Map(fooMock.Object), Times.Once());
    _mapperMock.Verify(x => x.Map(barMock.Object), Times.Once());
}

If I comment out the GetHashCode() override on either Foo or Bar, or on both, the test case passes. Or, if I don't use Mocks of Foo and Bar, the test case passes.

EDIT 3 I opened Moq Issue 347 against this problem, with more detailed test cases.

like image 800
Matt Mills Avatar asked Nov 04 '22 21:11

Matt Mills


1 Answers

Not sure what your Foo and Bar classes look like, but this test passes for me (Moq 4.0.10827.0, which is the newest one in NuGet)

using Moq;
using NUnit.Framework;

namespace ConsoleApplication1
{
    [TestFixture]
    public class Tests
    {
        [Test]
        public void TestIt()
        {
            // Arrange
            var _mapperMock = new Mock<IMapper<Foo, Bar>>();
            var fooMock = new Mock<Foo>();
            var barMock = new Mock<Bar>();

            _mapperMock.Setup(m => m.Map(fooMock.Object)).Returns(barMock.Object);
            _mapperMock.Setup(m => m.Map(barMock.Object)).Returns(fooMock.Object);

            // Act
            var bar = _mapperMock.Object.Map(fooMock.Object);
            var foo = _mapperMock.Object.Map(barMock.Object);

            // Assert
            Assert.AreSame(bar, barMock.Object);
            Assert.AreSame(foo, fooMock.Object);

            _mapperMock.Verify(x => x.Map(fooMock.Object), Times.Once());
            _mapperMock.Verify(x => x.Map(barMock.Object), Times.Once());
        }
    }

    public class Bar
    {
    }

    public class Foo
    {
    }
}
like image 97
CodingWithSpike Avatar answered Nov 08 '22 06:11

CodingWithSpike