Using Argument constraints that have a parameter node with an unnatural Fake throws "Exception: Argument types do not match"
See original GitHub issueHello! 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:
- Created 2 years ago
- Comments:7 (5 by maintainers)
Top 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 >Top Related Medium Post
No results found
Top Related StackOverflow Question
No results found
Troubleshoot Live Code
Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start FreeTop Related Reddit Thread
No results found
Top Related Hackernoon Post
No results found
Top Related Tweet
No results found
Top Related Dev.to Post
No results found
Top Related Hashnode Post
No results found
Top GitHub Comments
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.
Absolutely.
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:
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 thatA.CallTo
uses.)I’ve been playing and have reproduced with simpler tests, and even when inilning the constraint, e.g.:
But oddly
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
is fine. There’s one parameter,
x
Likewise
has only the one parameter
x
, butdoes 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).