Verification of list parameters

Solved!
Posted in General by Mel Grubb Wed Mar 02 2016 15:36:59 GMT+0000 (UTC)·4·Viewed 406 times

I'm trying to verify a call was made to a repository AddRange method. I can verify the method was called at all like this: ``` GetMockFor<IThingRepository>() .Verify(x => x.AddRange(It.IsAny<IEnumerable<Thing>>()), Times.Once); ``` I can verify that it was passed the expected two objects using some of Moq's partial matching like this: ``` GetMockFor<IThingRepository>() .Verify(x => x.AddRange(It.Is<IEnumerable<Thing>>(y => y.Count() == 2 && y.Any(z => z.ThingType == ThingType.Foo) && y.Any(z => z.ThingType == ThingType.Bar)))); ``` What I can't seem to do is get it to work using a more understandable partial matching syntax like this: ``` GetMockFor<IThingRepository>() .Verify(x => x.AddRange(Looks.Like(() => new[] { new Thing {ThingType = ThingType.Foo}, new Thing {ThingType = ThingType.Bar}, }))); ``` In every case, the code runs but the verify fails saying the method was never called. I looked through the documentation examples, and while they cover the partial matching functionality, they don't have an example of partial matching a collection of things like this. I know the partial matching can do hierarchies, but what about collections? I've even tried replacing the objects in the list with their own individual Looks.Likes: ``` GetMockFor<IThingRepository>() .Verify(x => x.AddRange( Looks.Like(() => new[] { Looks.Like(()=>new Thing {ThingType = ThingType.Foo}), Looks.Like(()=>new Thing {ThingType = ThingType.Bar}), }))); ``` Because of the way this particular code is written, the order of the elements being added is predictable, so I've eliminated that variable, although a partial match that could ignore ordering would be pretty awesome. What am I doing wrong here?
Matt Honeycutt
Mar 3, 2016

Interesting. I built a spec based on your description:

        [Test]
        public void then_it_matches_on_equivalent_enumerables()
        {
            var mock = GetMockFor<ITestService>();

            mock.Object.AddRange(new[]
            {
                new TestObject { ID = 1, Name = "Test 1" },
                new TestObject { ID = 2, Name = "Test 2" },
            });

            Assert.DoesNotThrow(() => mock.Verify(s => s.AddRange(Looks.Like(() => new[]
            {
                new TestObject { ID = 1, Name = "Test 1" },
                new TestObject { ID = 2, Name = "Test 2" },
            }))));
        }

That spec passes.

Here's the definition of ITestService:

        public interface ITestService
        {
            void AddRange(IEnumerable<TestObject> testObjects);
        }

However, I can break the spec if I instead call it with a List<T>:

        [Test]
        public void then_it_matches_on_equivalent_enumerables()
        {
            var mock = GetMockFor<ITestService>();

            var items = new List<TestObject>
            {
                new TestObject {ID = 1, Name = "Test 1"},
                new TestObject {ID = 2, Name = "Test 2"},
            };

            mock.Object.AddRange(items);

            Assert.DoesNotThrow(() => mock.Verify(s => s.AddRange(Looks.Like(() => new[]
            {
                new TestObject { ID = 1, Name = "Test 1" },
                new TestObject { ID = 2, Name = "Test 2" },
            }))));
        }

Are you calling AddRange with an array, or a something else?

In any case, this seems to be a bug in Looks.Like(), I just need to figure out the exact scenario so I can fix it.

Mel Grubb
Mar 3, 2016

Aha, that's it. AddRange is being passed an IEnumerable which is the result of an EF/Linq query, so it's technically and ObjectSet<T> at that point. I can see that throwing off the Looks.Like since the two enumerables are not the same type.

It would be acceptable to call ToArray or ToList on the collection before calling AddRange, since it will enumerate at that point anyway. I just tested it this way, and now the test works.

So the scenario is that Looks.Like sees an array on the one side, an ObjectSet<T> on the other, and decides that they're dissimilar enough to call it quits right then. I don't know what's going on behind the scenes here, but unless Looks.Like has some way to figure out that the method being called wanted any kind of IEnumerable, and can take that into account when comparing the two objects, I'd say this is a matter for documentation rather than code.

Thanks.

Matt Honeycutt
Mar 3, 2016

I'd like to make the partial matching handle this better. It should already know that both ends of the call are compatible enumerables since the compiler will enforce that. At the very least, it should throw a more useful exception.

I've opened up a Github issue for this, and I'll address it the next time I'm working on the code: https://github.com/MattHoneycutt/SpecsFor/issues/91


Matt Honeycutt marked this as solved
Matt Honeycutt
Mar 3, 2016

I'd like to make the partial matching handle this better. It should already know that both ends of the call are compatible enumerables since the compiler will enforce that. At the very least, it should throw a more useful exception.

I've opened up a Github issue for this, and I'll address it the next time I'm working on the code: https://github.com/MattHoneycutt/SpecsFor/issues/91


Matt Honeycutt marked this as solved
Markdown is allowed