FakeItEasy: Provide better feedback when call matcher throws

Please consider the following code and comments on it. As you can see in the examples, an exception in rule building makes finding the issue very hard, since you have no debug info at all, that is usually very rich with FIE

using System;
using FakeItEasy;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace FakeItEasyBugs
{
    [TestClass]
    public class UnitTest1
    {
        [TestMethod]
        public void EverythingOkTest()
        {
            ICanDoSomething fake = A.Fake<ICanDoSomething>();
            this.myApiCallGetsDataFromSomewhere(fake);
            A.CallTo(() => fake.CallMe(A<JObject>.That.Matches(o =>
                o["SomeName"].ToObject<string>() == "TestValue"))).MustHaveHappenedOnceExactly();
        }

        /// <summary>
        /// Test method FakeItEasyBugs.UnitTest1.SlightChanceToFindTheErrorTest threw exception: 
        /// FakeItEasy.ExpectationException: 
        /// 
        /// Assertion failed for the following call:
        /// FakeItEasyBugs.ICanDoSomething.CallMe(o: &lt;o =&gt; (o.get_Item("SomeName").ToObject() == "WrongValue")&gt;)
        /// Expected to find it once exactly but didn't find it among the calls:
        /// 1: FakeItEasyBugs.ICanDoSomething.CallMe(o: [[[]]])
        /// 
        /// 
        /// at FakeItEasy.Core.FakeAsserter.AssertWasCalled(Func`2 callPredicate, Action`1 callDescriber, CallCountConstraint callCountConstraint) in C:\projects\fakeiteasy\src\FakeItEasy\Core\FakeAsserter.cs:line 41
        /// at FakeItEasy.Configuration.RuleBuilder.MustHaveHappened(CallCountConstraint callCountConstraint) in C:\projects\fakeiteasy\src\FakeItEasy\Configuration\RuleBuilder.cs:line 174
        /// at FakeItEasyBugs.UnitTest1.SlightChanceToFindTheErrorTest()
        /// </summary>
        [TestMethod]
        public void SlightChanceToFindTheErrorTest()
        {
            ICanDoSomething fake = A.Fake<ICanDoSomething>();
            this.myApiCallGetsDataFromSomewhere(fake);
            A.CallTo(() => fake.CallMe(A<JObject>.That.Matches(o =>
                    o["SomeName"].ToObject<string>() == "WrongValue")))
                .MustHaveHappenedOnceExactly();
        }

        /// <summary>
        /// Test method FakeItEasyBugs.UnitTest1.HaveNoIdeaWhatGoesWrongTest threw exception: 
        /// System.NullReferenceException: Object reference not set to an instance of an object.
        /// at lambda_method(Closure , JObject )
        /// at System.Linq.Enumerable.All[TSource](IEnumerable`1 source, Func`2 predicate)
        /// at FakeItEasy.Configuration.RuleBuilder.RuleMatcher.Matches(IFakeObjectCall call) in C:\projects\fakeiteasy\src\FakeItEasy\Configuration\RuleBuilder.cs:line 322
        /// at System.Linq.Enumerable.Count[TSource](IEnumerable`1 source, Func`2 predicate)
        /// at FakeItEasy.Core.FakeAsserter.AssertWasCalled(Func`2 callPredicate, Action`1 callDescriber, CallCountConstraint callCountConstraint) in C:\projects\fakeiteasy\src\FakeItEasy\Core\FakeAsserter.cs:line 34
        /// at FakeItEasy.Configuration.RuleBuilder.MustHaveHappened(CallCountConstraint callCountConstraint) in C:\projects\fakeiteasy\src\FakeItEasy\Configuration\RuleBuilder.cs:line 172
        /// at FakeItEasyBugs.UnitTest1.HaveNoIdeaWhatGoesWrongTest()
        /// </summary>
        [TestMethod]
        public void HaveNoIdeaWhatGoesWrongTest()
        {
            ICanDoSomething fake = A.Fake<ICanDoSomething>();
            this.myApiCallGetsDataFromSomewhere(fake);
            A.CallTo(() => fake.CallMe(A<JObject>.That.Matches(o =>
                    o["Misspelled"].ToObject<string>() == "TestValue")))
                .MustHaveHappenedOnceExactly();
        }

        /// <summary>
        /// Test method FakeItEasyBugs.UnitTest1.TryingToHackSomeDebugInfoTest threw exception: 
        /// FakeItEasy.ExpectationException: 
        /// 
        /// Assertion failed for the following call:
        /// FakeItEasyBugs.ICanDoSomething.CallMe(o: &lt;{
        /// "SomeName": "TestValue"
        /// }&gt;)
        /// Expected to find it once exactly but didn't find it among the calls:
        /// 1: FakeItEasyBugs.ICanDoSomething.CallMe(o: [[[]]])
        /// 
        /// 
        /// at FakeItEasy.Core.FakeAsserter.AssertWasCalled(Func`2 callPredicate, Action`1 callDescriber, CallCountConstraint callCountConstraint) in C:\projects\fakeiteasy\src\FakeItEasy\Core\FakeAsserter.cs:line 41
        /// at FakeItEasy.Configuration.RuleBuilder.MustHaveHappened(CallCountConstraint callCountConstraint) in C:\projects\fakeiteasy\src\FakeItEasy\Configuration\RuleBuilder.cs:line 174
        /// at FakeItEasyBugs.UnitTest1.TryingToHackSomeDebugInfoTest()
        /// </summary>
        [TestMethod]
        public void TryingToHackSomeDebugInfoTest()
        {
            ICanDoSomething fake = A.Fake<ICanDoSomething>();
            JObject fakeParamRecieved = null;
            A.CallTo(() => fake.CallMe(A<JObject>.Ignored)).Invokes((JObject o) => fakeParamRecieved = o);
            this.myApiCallGetsDataFromSomewhere(fake);
            A.CallTo(() => fake.CallMe(A<JObject>.That.Matches(o =>
                    o["SomeName"].ToObject<string>() == "WrongValue", io => io.Write(JsonConvert.SerializeObject(fakeParamRecieved, Formatting.Indented)))))
                .MustHaveHappenedOnceExactly();
        }

        /// <summary>
        /// Test method FakeItEasyBugs.UnitTest1.MyDebugInfoIsCompletelyLostTest threw exception: 
        /// System.NullReferenceException: Object reference not set to an instance of an object.
        /// at lambda_method(Closure , JObject )
        /// at System.Linq.Enumerable.All[TSource](IEnumerable`1 source, Func`2 predicate)
        /// at FakeItEasy.Configuration.RuleBuilder.RuleMatcher.Matches(IFakeObjectCall call) in C:\projects\fakeiteasy\src\FakeItEasy\Configuration\RuleBuilder.cs:line 322
        /// at System.Linq.Enumerable.Count[TSource](IEnumerable`1 source, Func`2 predicate)
        /// at FakeItEasy.Core.FakeAsserter.AssertWasCalled(Func`2 callPredicate, Action`1 callDescriber, CallCountConstraint callCountConstraint) in C:\projects\fakeiteasy\src\FakeItEasy\Core\FakeAsserter.cs:line 34
        /// at FakeItEasy.Configuration.RuleBuilder.MustHaveHappened(CallCountConstraint callCountConstraint) in C:\projects\fakeiteasy\src\FakeItEasy\Configuration\RuleBuilder.cs:line 172
        /// at FakeItEasyBugs.UnitTest1.MyDebugInfoIsCompletelyLostTest()
        /// </summary>
        [TestMethod]
        public void MyDebugInfoIsCompletelyLostTest()
        {
            ICanDoSomething fake = A.Fake<ICanDoSomething>();
            JObject fakeParamRecieved = null;
            A.CallTo(() => fake.CallMe(A<JObject>.Ignored)).Invokes((JObject o) => fakeParamRecieved = o);
            this.myApiCallGetsDataFromSomewhere(fake);
            A.CallTo(() => fake.CallMe(A<JObject>.That.Matches(o =>
                    o["Misspelled"].ToObject<string>() == "WrongValue", io => io.Write(JsonConvert.SerializeObject(fakeParamRecieved, Formatting.Indented))))) // IO is ignored comepletely
                .MustHaveHappenedOnceExactly();
        }

        [TestMethod]
        public void WhatIWhishForTest()
        {
            ICanDoSomething fake = A.Fake<ICanDoSomething>();
            this.myApiCallGetsDataFromSomewhere(fake);
            A.CallTo(() => fake.CallMe(A<JObject>.That.Matches(o => o["Misspelled"].ToObject<string>() == "TestValue"
                    // Here I can print any kind of info about the call I expected with the actual value
                    //, (IFakeObjectCall c, IOutputWriter io) => io.Write(JsonConvert.SerializeObject(c.Arguments, Formatting.Indented))
                )))
                .MustHaveHappenedOnceExactly();
        }

        /// <summary>
        /// This method gives me data I have no clue about!
        /// </summary>
        private void myApiCallGetsDataFromSomewhere(ICanDoSomething fake)
        {
            JObject testObject = new JObject();
            testObject.Add("SomeName", JToken.FromObject("TestValue"));
            fake.CallMe(testObject);
        }
    }

    public interface ICanDoSomething
    {
        void CallMe(JObject o);
    }
}

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Comments: 16 (16 by maintainers)

Most upvoted comments

A means to write your own debug info is IMO the more important thing, to get the info you need rather then having FIE provide a converter for every class out there.

Well, it’s what custom formatters are for 😉

This change has been released as part of FakeItEasy 4.7.0.

Thanks, @Mertsch. Look for your name in the release notes! 🥇

Thank you very much @blairconrad and @thomaslevesque . You are awesome.

If a call matcher throws, it’s still appropriate for the assertion to fail (not just not match, but fail with an exception), but it should clearly indicate that the matcher threw (describing it as well as possible) and it should still indicate which calls have been made to the Fake.