NSubstitute icon indicating copy to clipboard operation
NSubstitute copied to clipboard

Compiler error when using Arg.Is<Arg.AnyType>

Open srgoapp opened this issue 1 year ago • 5 comments

Describe the bug When using Arg.Is<Arg.AnyType>(expression), depending on the expression, the code can't compile. The compiler error is Error CS0121 : The call is ambiguous between the following methods or properties: 'Arg.Is<T>(Expression<Predicate<T>>)' and 'Arg.Is<T>(Expression<Predicate>)'

To Reproduce

    public interface ISomethingWithGenerics
    {
        void SomeAction<TState>(int level, TState state);
        string SomeFunction<TState>(int level, TState state);
        void SomeActionWithGenericConstraints<TState>(int level, TState state) where TState : IEnumerable<int>;
        string SomeFunctionWithGenericConstraints<TState>(int level, TState state) where TState : IEnumerable<int>;
    }
    
    [Fact]
    public void Is_matcher_works_with_AnyType()
    {
        ISomethingWithGenerics something = Substitute.For<ISomethingWithGenerics>();

        something.SomeFunction(Arg.Any<int>(), Arg.Any<Arg.AnyType>()).Returns("matched");

        var result = something.SomeFunction(7, 3409);
        Assert.Equal("matched", result);
        something.Received(1).SomeFunction(Arg.Any<int>(), Arg.Is<Arg.AnyType>(state => state.ToString() == 3409.ToString()));
    }

The lambda expression: state => (int)state == 3409) is legal The lambda expression: state => state.ToString() == 3409.ToString() produces the compile error:
NotificationServiceTest.cs(315, 64): [CS0121] The call is ambiguous between the following methods or properties: 'Arg.Is<T>(Expression<Predicate<T>>)' and 'Arg.Is<T>(Expression<Predicate<object>>)'

Expected behaviour No compiler error

Environment:

  • NSubstitute version: 5.1.0
  • Platform: .Net 7 project (windows)

Additional context Trying to migrate away from Moq.

        Func<object, Type?, bool> state = (v, t) => v.ToString()!.Contains(expectedMessage);
        
        logger.Verify(
            x => x.Log(
                It.Is<LogLevel>(l => l == expectedLogLevel),
                It.IsAny<EventId>(),
                It.Is<It.IsAnyType>((v, t) => state(v, t)),
                It.IsAny<Exception>(),
                It.Is<Func<It.IsAnyType, Exception?, string>>((v, t) => true)), (Times)times);

srgoapp avatar Nov 23 '23 09:11 srgoapp

It's possible to remove the compile error by changing

something.Received(1).SomeFunction(Arg.Any<int>(), Arg.Is<Arg.AnyType>(state => state.ToString() == 3409.ToString()));

to

Func<Arg.AnyType, bool> match = v => v.ToString() == 3409.ToString();
something.Received(1).SomeFunction(Arg.Any<int>(), Arg.Is<Arg.AnyType>(state => match(state)));

but no match will be found.

Expected to receive exactly 1 call matching:
	SomeFunction<Arg+AnyType>(any Int32, state => Invoke(value(UnitTests.Notifications.NotificationServiceTest+<>c__DisplayClass15_0).match, state))
Actually received no matching calls.
Received 1 non-matching call (non-matching arguments indicated with '*' characters):
	SomeFunction<Int32>(7, *3409*)

srgoapp avatar Nov 23 '23 09:11 srgoapp

Not sure what you are trying to do.

The Arg.AnyType was introduced to say "I dont care which type, I just want to test that the method was called". So the combination of Arg.Is with Arg.AnyType doesnt make sense for me.

You want to check that SomeFunction was called with the integer 3409, so just write:

    something.Received(1)
      .SomeFunction(Arg.Any<int>(), Arg.Is<int>(3409));

which can be shortened to:

    something.Received(1)
      .SomeFunction(Arg.Any<int>(), 3409);

GeraldLx avatar Nov 24 '23 11:11 GeraldLx

Arg.Is with Arg.AnyType is for testing the generic parameter. If i would like to mock the ILogger method and assert that the logger was called with a log message that contained a specific message.

void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter);

Arg.Is<Arg.AnyType>(state => state.ToString().Contains(expectedMessage))

srgoapp avatar Nov 25 '23 22:11 srgoapp

I ran into this exact same issue while converting from Moq, except I was using StartsWith(expectedMessage). However, the message I see from the compiler is CS1660 Cannot convert the lambda expression to type 'Arg.AnyType' because it is not a delegate type.

scottrudy avatar Dec 20 '23 20:12 scottrudy

The Arg.AnyType was introduced to say "I dont care which type, I just want to test that the method was called". So the combination of Arg.Is with Arg.AnyType doesnt make sense for me.

Funny, as there is some case in the code for this?

https://github.com/nsubstitute/NSubstitute/blob/1f2fb0f05dd5ae581d0467e963dc81c96ddac261/src/NSubstitute/Arg.cs#L56

304NotModified avatar Apr 30 '24 22:04 304NotModified