RobC
RobC

Reputation: 1415

Trouble with Mock.Assert() for sequential calls with different argument values to mock

Could someone please take a look at the demo code below and let me know if what I'm seeing is due to error on my part or a Telerik issue?

I'm using Telerik.JustMock v. 2014.1.1519.1. and Microsoft.VisualStudio.QualityTools.UnitTestFramework v. 10.0.0.0.

As the code comments note, I get the expected results when the id variables are equal (one call for each of the ids), but not when they're different. When I step through the first test I can see the expected calls being made, but JustMock then tells me they weren't made.

I'll appreciate any constructive thoughts. Hopefully this isn't a case of me not getting enough sleep...

[TestClass]
public class RunnerTests
{
[TestMethod]
public void MakeTwoCallsDifferentIdsFails()
{
    int idOne=1;
    int idTwo=2;

    DataTable dt=new DataTable();
    dt.Columns.Add("Id");
    dt.Rows.Add(idOne);
    dt.Rows.Add(idTwo);

    IProcessor mock = Mock.Create<IProcessor>();
    Runner runner = new Runner(mock);
    runner.Process(dt);

    Mock.Assert(()=>mock.Process(Arg.IsAny<MyArgs>()), Occurs.Exactly(2));
    //The following two asserts fail (with 0 calls made to mock), regardless of sequence:
    Mock.Assert(()=>mock.Process(Arg.Matches<MyArgs>     
       (d=>d.Id==idOne)),Occurs.Once());
    Mock.Assert(()=>mock.Process(Arg.Matches<MyArgs>
       (d=>d.Id==idTwo)),Occurs.Once());
}

[TestMethod]
public void MakeTwoCallsSameIdPasses()
{
    //ids intentionally equal:
    int idOne=1;
    int idTwo=1;

    DataTable dt=new DataTable();
    dt.Columns.Add("Id");
    dt.Rows.Add(idOne);
    dt.Rows.Add(idTwo);

    IProcessor mock = Mock.Create<IProcessor>();
    Runner runner = new Runner(mock);
    runner.Process(dt);

    //all asserts pass:
    Mock.Assert(()=>mock.Process(Arg.IsAny<MyArgs>()), Occurs.Exactly(2));
    //The following two pass:
    Mock.Assert(()=>mock.Process(Arg.Matches<MyArgs>     
        (d=>d.Id==idOne)),Occurs.Exactly(2));
    Mock.Assert(()=>mock.Process(Arg.Matches<MyArgs>
        (d=>d.Id==idTwo)),Occurs.Exactly(2));
}
}

public interface IProcessor
{
    void Process(MyArgs args);
}

public class MyArgs
{
    public void UpdateId(int newId)
    {
        this.Id = newId;
    }

    public int Id {get; private set;}
}

public class Runner
{
    private IProcessor processor;

    public Runner(IProcessor processor)
    {
        this.processor=processor;
    }

    public void Process(DataTable dt)
    {
        MyArgs args = new MyArgs();

        foreach(DataRow row in dt.Rows)
        {
            int id = Convert.ToInt32(row["Id"]);
            args.UpdateId(id);
            processor.Process(args);
        }
    }
}

EDIT: In the test method that fails, if I completely remove one of the int variables and explicitly assert that the other was called exactly once, the test passes. Things seem to go south only when I throw that second, different value into the mix.

Upvotes: 0

Views: 448

Answers (2)

Stefan Dragnev
Stefan Dragnev

Reputation: 14473

The mock records that it was called twice with the same instance of MyArgs. The fact that the instance contents are different between the two calls is lost to it, though. When you get to the point where you assert the calls, the recorded MyArgs argument has an Id equal to 2 in the failing test.

If you change your Process method to

public void Process(DataTable dt)
{
    foreach(DataRow row in dt.Rows)
    {
        MyArgs args = new MyArgs();
        int id = Convert.ToInt32(row["Id"]);
        args.UpdateId(id);
        processor.Process(args);
    }
}

both tests will pass.

EDIT:

You can get the desired behavior using JustMock by stating your expectations in arrangements, like so:

[TestMethod]
public void MakeTwoCallsDifferentIdsFails()
{
    int idOne = 1;
    int idTwo = 2;

    DataTable dt = new DataTable();
    dt.Columns.Add("Id");
    dt.Rows.Add(idOne);
    dt.Rows.Add(idTwo);

    IProcessor mock = Mock.Create<IProcessor>();
    Mock.Arrange(() => mock.Process(Arg.Matches<MyArgs>(d => d.Id == idOne))).OccursOnce();
    Mock.Arrange(() => mock.Process(Arg.Matches<MyArgs>(d => d.Id == idTwo))).OccursOnce();

    Runner runner = new Runner(mock);
    runner.Process(dt);

    Mock.Assert(mock);
}

The reason why this works is that the occurrence expectations are marked as met as soon as the right calls happen, so the predicate to Arg.Matches works with the live values of the arguments passed to the method, not post fact when the saved argument instances may have mutated.

Upvotes: 0

RobC
RobC

Reputation: 1415

Since I couldn't get our mocking framework of choice (JustMock) to accomplish what I wanted, I ended up applying KISS, forgot about the mocking framework for this test, and went with something like the following:

I changed my test method to look like this:

    [TestMethod]
    public void TwoCallsDifferentIds()
    {
        int idOne = 1;
        int idTwo = 2;

        DataTable dt = new DataTable();
        dt.Columns.Add("Id");
        dt.Rows.Add(idOne);
        dt.Rows.Add(idTwo);
        FakeProcessor processor = new FakeProcessor();
        Runner runner = new Runner(processor);
        runner.Process(dt);

        Assert.AreEqual(2, processor.MyArgsIds.Count);
        Assert.AreEqual(1, processor.MyArgsIds[0]);
        Assert.AreEqual(2, processor.MyArgsIds[1]);
    }

and added the following fake:

public class FakeProcessor : IProcessor
{
    private IList<int> mList = new List<int>();

    public IList<int> MyArgsIds 
    {
        get { return mList; }
    }

    public void Process(MyArgs args)
    {
        mList.Add(args.Id);
    }
}

Not as slick, perhaps, but it got me the test I wanted.

Upvotes: 0

Related Questions