question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Using Argument constraints that have a parameter node with an unnatural Fake throws "Exception: Argument types do not match"

See original GitHub issue

Hello! I started using FakeItEasy on my most recent project instead of Moq (found it to be much more readable!) and it has been great so far, but I think I just stumbled upon a bug when migrating to this library: The matcher for assigning different results for different method arguments doesn’t seem to work when the argument is an object, even though it works when it is a value like string, int, and bool.

I made some test cases to help fixing it, for comparison with argument matching with the static A.CallTo call, and with what I had on Moq:

using FakeItEasy;
using Moq;
using System;
using System.Linq.Expressions;
using System.Threading.Tasks;
using Xunit;

namespace FakeItEasyIssue.Testing
{
    public class CallToVsCallsToTest
    {
        public class TextServiceRequest
        {
            public string Path { get; set; }
            public string OtherParam { get; set; }

            public TextServiceRequest(string path, string otherParam)
            {
                Path = path;
                OtherParam = otherParam;
            }
        }

        public interface ITextService
        {
            Task<string> GetContent(string path, string otherParam);
            Task<string> GetContent(TextServiceRequest request);
        }

        [Fact]
        public void TestUsingStaticCallToWithPrimitiveValue()
        {
            var textService = A.Fake<ITextService>(x => x.Strict());

            A.CallTo(textService).WithReturnType<Task<string>>()
                .Where(x => (x.Arguments[0] as string).Contains("somePath")
                    && (x.Arguments[1] as string).StartsWith("someValue"))
                .ReturnsLazily(() => "success!");

            var result = textService.GetContent("base/somePath/path", "someValue-othervalue").Result;

            A.CallTo(textService).WithReturnType<Task<string>>()
                .Where(x => (x.Arguments[0] as string).Contains("somePath")
                    && (x.Arguments[1] as string).StartsWith("someValue"))
                .MustHaveHappened();

            Assert.Equal("success!", result); // works
        }

        [Fact]
        public void TestUsingFakeCallsToWithPrimitiveValue()
        {
            var mock = new Fake<ITextService>();

            Expression<Func<ITextService, Task<string>>> getContent =
                x => x.GetContent(A<string>.That.Contains("somePath"), A<string>.That.StartsWith("someValue"));

            mock.CallsTo(getContent)
                .ReturnsLazily(() => "success!");

            var result = mock.FakedObject.GetContent("base/somePath/path", "someValue-othervalue").Result;

            mock.CallsTo(getContent).MustHaveHappened();

            Assert.Equal("success!", result); // works
        }

        [Fact]
        public void TestUsingStaticCallToWithObject()
        {
            var textService = A.Fake<ITextService>(x => x.Strict());

            A.CallTo(textService).WithReturnType<Task<string>>()
                .Where(x => (x.Arguments[0] as TextServiceRequest).Path.Contains("somePath")
                    && (x.Arguments[0] as TextServiceRequest).OtherParam.StartsWith("someValue"))
                .ReturnsLazily(() => "success!");

            var result = textService.GetContent(new TextServiceRequest("base/somePath/path", "someValue-othervalue")).Result;

            A.CallTo(textService).WithReturnType<Task<string>>()
                .Where(x => (x.Arguments[0] as TextServiceRequest).Path.Contains("somePath")
                    && (x.Arguments[0] as TextServiceRequest).OtherParam.StartsWith("someValue"))
                .MustHaveHappened();

            Assert.Equal("success!", result); // works
        }

        [Fact]
        public void TestUsingFakeCallsToWithObject()
        {
            var mock = new Fake<ITextService>();

            Expression<Func<ITextService, Task<string>>> getContent =
                x => x.GetContent(A<TextServiceRequest>
                    .That.Matches(opt
                        => opt.Path.Contains("somePath") && opt.OtherParam.StartsWith("someValue")));
                        // Matches gets an expression that returns a bool, as is requested

            
            mock.CallsTo(getContent) // Exception: Argument types do not match
                .ReturnsLazily(() => "success!");

            var result = mock.FakedObject.GetContent(new TextServiceRequest("base/somePath/path", "someValue-othervalue")).Result;

            mock.CallsTo(getContent).MustHaveHappened();

            Assert.Equal("success!", result); // fails on the first CallsTo
        }

        [Fact]
        public void TestUsingMoqWithObject()
        {
            var mock = new Mock<ITextService>();

            Expression<Func<ITextService, Task<string>>> getContent =
                x => x.GetContent(It.Is<TextServiceRequest>(req => req.Path.Contains("somePath")
                    && req.OtherParam.StartsWith("someValue")));

            mock.Setup(getContent)
                .ReturnsAsync(() => "success!");

            var result = mock.Object.GetContent(new TextServiceRequest("base/somePath/path", "someValue-othervalue")).Result;

            mock.Verify(getContent);

            Assert.Equal("success!", result); // works
        }

    }
}

Issue Analytics

  • State:closed
  • Created 2 years ago
  • Comments:7 (5 by maintainers)

github_iconTop GitHub Comments

2reactions
thomaslevesquecommented, Mar 30, 2021

Thanks for the report, @raff-run. Looks like you encountered a very real bug. It’s probably been there for a while, but since so few people use unnatural fakes, it was never discovered.

@thomaslevesque, seems worth fixing to me, no?

Absolutely.

I think a cheap fix would be to adjust ParameterValueReplacementVisitor.VisitParameter to only replace a parameter once. It assumes that the first parameter it visits would be the one that’s supposed to be replaced by the Fake, but I doubt this behaviour is going to change.

Is there no way that the same parameter would appear multiple times in the expression? I think we should replace if and only if it’s the parameter of the root lambda expression (there should be only one). Something like this:


        private class ParameterValueReplacementVisitor : ExpressionVisitor
        {
            private readonly ParameterExpression parameterToReplace;
            private readonly object fake;

            public ParameterValueReplacementVisitor(ParameterExpression parameterToReplace, object fake)
            {
                this.fake = fake;
                this.parameterToReplace = parameterToReplace;
            }

            [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Justification = "It's not public, and will never be called with a null value")]
            protected override Expression VisitParameter(ParameterExpression node)
            {
                if (Equals(node, this.parameterToReplace))
                    return Expression.Constant(this.fake, node.Type);
                return node;
            }
        }
1reaction
blairconradcommented, Mar 30, 2021

Hi, @raff-run. Thanks for your interest in FakeItEasy. Sorry to hear you’re having some frustration. I enjoyed the detailed reproduction example. It made it easy to follow along.

It seems that this only applies to so-called “unnatural Fakes”. These don’t get nearly the attention the “usual” Fakes do. As you’ve shown, the A.CallTo(…) style is working. (Because the code that throws the error is essentially massaging the provided call expression into the format that A.CallTo uses.)

I’ve been playing and have reproduced with simpler tests, and even when inilning the constraint, e.g.:

mock.CallsTo(x => x.GetContent(A<TextServiceRequest>.That.Matches(o => true))) // Exception: Argument types do not match
    .ReturnsLazily(() => "success!");

But oddly

mock.CallsTo(x => x.GetContent(A<TextServiceRequest>.That.IsNotNull()))
     .ReturnsLazily(() => "success!");

does not throw.

*debugs for a bit*

AHA!

I think problem is that CallExpressionParser.ReplaceParameterWithFake uses a ParameterValueReplacementVisitor that will replace any parameter node with the Fake. When the constraint expression is simple (does not itself have parameter nodes), then everything’s fine. This is why

 x => x.GetContent(A<string>.That.Contains("somePath"), A<string>.That.StartsWith("someValue"))

is fine. There’s one parameter, x

Likewise

x => x.GetContent(A<TextServiceRequest>.That.IsNotNull())

has only the one parameter x, but

x => x.GetContent(A<TextServiceRequest>.That.Matches(o => true))

does not work. o is a second parameter. Of the wrong type (which is actually a blessing in this case, as it would not do the right thing regardless).

Read more comments on GitHub >

github_iconTop Results From Across the Web

LINQ, "Argument types do not match" error, what does it ...
"Argument types do not match" error is usually caused by the un-matching type between the property we would like to bind and its...
Read more >
"Argument types do not match" when using ternary ...
Basically, this line in a select statement causes "Argument types do not match" to be thrown when .ToList() is called on the query:....
Read more >
Releases · FakeItEasy/FakeItEasy
Using Argument constraints that have a parameter node with an unnatural Fake throws "Exception: Argument types do not match" (#1825) ...
Read more >
False positive warning 'Argument types do not match ...
Create Node.js express template app. For logger and cookieParser there is a warning 'Argument types do not match parameters'. WebStorm 2020.2 EAP
Read more >
Hibernate ORM 5.6.15.Final User Guide
Setting it on the java.time classes throws the following exception: org.hibernate. ... The entity class should have a no-argument constructor.
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found