141

Given the following interface:

public interface IFoo
{
    bool Foo(string a, bool b = false);
}

Attempting to mock it using Moq:

var mock = new Mock<IFoo>();
mock.Setup(mock => mock.Foo(It.IsAny<string>())).Returns(false);

gives the following error at compile time:

An expression tree may not contain a call or invocation that uses optional arguments

I've found the issue above raised as an enhancement in Moq's list of issues and it appears to be assigned to the 4.5 release (whenever that is).

My question is: what should I do given that the above is not going to be fixed anytime soon? Are my options only to either explicitly set the default value of the optional parameter every time I mock it (which kind of defeats the point of specifying one in the first place) or to create an overload without the bool (like what I would have done prior to C# 4)?

Or has anyone come across a more clever way to overcome this issue?

leppie
  • 115,091
  • 17
  • 196
  • 297
Appulus
  • 18,630
  • 11
  • 38
  • 46

6 Answers6

104

I believe your only choice right now is to explicitly include the bool parameter in the setup for Foo.

I don't think it defeats the purpose of specifying a default value. The default value is a convenience for calling code, but I think that you should be explicit in your tests. Say you could leave out specifying the bool parameter. What happens if, in future, someone changes the default value of b to true? This will lead to failing tests (and rightfully so), but they will be more difficult to fix because of the hidden assumption that b is false. Explicitly specifying the bool parameter has another benefit: it improves the readability of your tests. Someone going through them will quickly know that there's one Foo function that accepts two parameters. That's my 2 cents, at least :)

As for specifying it every time you mock it, don't duplicate code: create and/or initialise the mock in a function, so that you only have a single point of change. If you really want to, you can overcome Moq's apparent short-coming here by duplicating Foo's parameters into this initialisation function:

public void InitFooFuncOnFooMock(Mock<IFoo> fooMock, string a, bool b = false)
{
    if(!b)
    {
        fooMock.Setup(mock => mock.Foo(a, b)).Returns(false);
    }
    else
    {
        ...
    }
}
Chris Mantle
  • 6,595
  • 3
  • 34
  • 48
  • 1
    Excellent answer; I already went ahead and specified it explicitly in my mocks but your answer confirms in a very clear and logical fashion why I should do it this way. Thanks, @Chris. – Appulus Oct 20 '12 at 19:38
  • 12
    changing a default parameter "should" break tests. Not having tests fail when a default is changed can be a sign of bad testing. The code may use the defaults but the tests do not? – Pop Catalin Apr 17 '13 at 07:31
  • Been a while, but I've tried this approach with Moq when trying to mock an interface (IDConnection in Dapper) and I'm still getting the same error. Any ideas why? Sample line: mockDB.Setup(x => x.Query(It.IsAny(), It.IsAny(), It.IsAny(), false, 600)).Returns(new List()); The last two values are the optional parameters on the method I'm setting up. – Raelshark Aug 07 '13 at 22:03
  • Hmmm. Is Query an extension method? Could you try using `It.Is(b => b == false), It.Is(i => i == 600)` in place of `false, 600`, please? Are you certain you're not missing another optional parameter after `600`? – Chris Mantle Aug 08 '13 at 09:47
  • Ah, `Query` is an extension method by the looks of it. You won't be able to mock it :( Use [Dapper Wrapper](https://github.com/half-ogre/dapper-wrapper/tree/master/DapperWrapper) - you can mock `Query` on `IDbExecutor`. – Chris Mantle Aug 08 '13 at 10:40
  • 6
    Arrgggh! The horrible `if (!x) {} else {}` anti-pattern :) – nicodemus13 Apr 15 '14 at 17:12
  • @PopCatalin Yes, changing a default parameter should break tests. I've changed my wording to clarify what I meant (better late than never :) – Chris Mantle Sep 11 '15 at 12:38
  • 1
    @nicodemus13 Yes, but I was trying to keep the code example as close to the OP's example in the question as possible. I wouldn't necessarily advocate it :) – Chris Mantle Sep 11 '15 at 12:39
  • Unfortunately, providing an explicit value for the optional parameter doesn't work in the case that it's a delegate argument. You still get a MissingMethodException. – Paul Taylor Feb 15 '18 at 16:26
  • 2
    The larger issue is that if I have an existing test base (with lots of tests) and I add an optional parameter, I should not be required to change all of my existing tests to be explicit -- just as I do not change my existing production code. – Kerry Jan 13 '22 at 18:55
10

Using Moq version 4.10.1 I have been able to do the following

With Interface:

public interface IFoo
{
    bool Foo(string a, bool b = false);
}

And Mock

var mock = new Mock<IFoo>();
mock.Setup(mock => mock.Foo(It.IsAny<string>(), It.IsAny<bool>())).Returns(false);

Resolves a call to Foo with the first parameter okay

CF5
  • 1,123
  • 9
  • 19
  • True, but the answer is still useful because it fails at verify. mock.Verify(mock => mock.Foo("test string"), Times.Once()); //generates the error mock.Verify(mock => mock.Foo("test string", false), Times.Once(); ///works fine – MikeF Aug 23 '23 at 13:17
9

Just encountered this issue today, Moq doesn't support this use case. So, seems that overriding the method would be sufficient for this case.

public interface IFoo
{
    bool Foo(string a);

    bool Foo(string a, bool b);
}

Now both methods are available and this example would work:

var mock = new Mock<IFoo>();
mock.Setup(mock => mock.Foo(It.IsAny<string>())).Returns(false);
Gil Grossman
  • 139
  • 2
  • 8
2

If you are trying to mock a call to a method such as DoSomething(string arg = null) for which you are not passing in an argument, simply use ? like so:

mockClass.Setup(x => x.DoSomething(It.IsAny<string?>())).ReturnsAsync(mockResponse);
Sean McCarthy
  • 91
  • 1
  • 6
0

In my case, my problem was that I forgot to include an It.IsAny<string>() call for one of my optional parameters. Once I had an It.Is... call for all of my parameters (including the optional ones) it compiled just fine.

0

Let's say your method is defined in your interface like below

Task<IDataResult<StripeSubModel>> CancelSub(List<string> list, bool? revert = false);

You can define your mock service like below

mockService.Setup(s => s.CancelSub(It.IsAny<List<string>>(), It.IsAny<bool>()))
Ahmet Firat Keler
  • 2,603
  • 2
  • 11
  • 22