Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Testing default interface methods in a mocked interface #703

Open
ursmeili opened this issue Sep 7, 2022 · 7 comments
Open

Testing default interface methods in a mocked interface #703

ursmeili opened this issue Sep 7, 2022 · 7 comments
Labels
feature-request Request for a new NSubstitute feature

Comments

@ursmeili
Copy link

ursmeili commented Sep 7, 2022

I would like to mock an interface which features a default interface method (introduced in C#8).

However, it seems like the default interface method also gets mocked away, so in the example below, the method HasNumber() is never called, and the test fails.

Is there any solution for it already? If not, I'd request a feature to make this work.

using System.Linq;
using FluentAssertions;
using NSubstitute;
using Xunit;

public interface IFoo
{
    int[] Numbers { get; }

    bool HasNumber(int n)
    {
        return this.Numbers.Contains(n);
    }
}

public class Test
{
    [Fact]
    public void HasNumber()
    {
        // arrange
        var mock = Substitute.For<IFoo>();
        mock.Numbers.Returns(new[] { 1, 2 });

        // act
        var actual = mock.HasNumber(1);

        // assert
        actual.Should().BeTrue();
    }
}
@ursmeili
Copy link
Author

Note that Moq supports this since April 2022 (see https://jeremybytes.blogspot.com/2019/09/c-8-interfaces-unit-testing-default.html). It's a pity NSubstitute does not support it.

@dtchepak
Copy link
Member

Hi @ursmeili ,
Sorry, this is not implemented. Do you have any time to attempt a PR?

@dtchepak dtchepak added the feature-request Request for a new NSubstitute feature label Oct 15, 2022
@ursmeili
Copy link
Author

@dtchepak sorry, no, I have no spare time currently.

@andreminelli
Copy link

This is still an issue, right?

If so, is there some starting tips for a PR to handle this, @dtchepak ? I can try to contribute, but I have no idea about where to start...

@dtchepak
Copy link
Member

dtchepak commented Apr 1, 2023

Hi @andreminelli ,

Here's a test to start:

#if NET5_0_OR_GREATER
using System.Linq;
using NUnit.Framework;

namespace NSubstitute.Acceptance.Specs.FieldReports
{
    public class InterfaceWithDefaults
    {
        // From https://github.com/nsubstitute/NSubstitute/issues/703
        public interface IHaveDefaultMembers
        {
            int[] Numbers { get; }
            bool HasNumber(int n) => this.Numbers.Contains(n);
        }

        [Test]
        public void Test_Defaults()
        {
            var sub = Substitute.For<IHaveDefaultMembers>();
            sub.Numbers.Returns(new[] { 1, 2 });
            //sub.When(x => x.HasNumber(Arg.Any<int>())).CallBase();

            Assert.That(sub.HasNumber(2), Is.True);
        }
    }
}
#endif

With the CallBase() line uncommented the test fails with:

NSubstitute.Exceptions.CouldNotConfigureCallBaseException : Cannot configure the base method call as base method implementation is missing. You can call base method only if you create a class substitute and the method is not abstract.

This is thrown when ICall.CanCallBase is false, so I'm guessing first thing is to work out whether that state is correct. It might be a matter of updating that to detect default members on interfaces. If not we might need to look at whether we need to do anything specific to the generated proxy to allow this (maybe can take inspiration from Moq's approach here?).

Hope this helps! Thanks so much for taking a look at this. 🙇

@andreminelli
Copy link

@dtchepak, very nice! This is much more than I was expecting 😊

But only thank me if - or "when", let's get optimistic - I could come up with some solution.

@andreminelli
Copy link

@dtchepak , as a first look, even after solving that state problem we will probably have an issue with Castle.DynamicProxy which still does not support default members on interfaces, too :(

The approach made on Moq uses System.Reflection.Emit to workaround this lack on Castle.DynamicProxy. I would rather check if we could get this support on it before trying something like that in NSubstitute - I have poked the related issue to get an update.

Meanwhile I will study more the NSubstitute repo and check that state problem.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature-request Request for a new NSubstitute feature
Projects
None yet
Development

No branches or pull requests

3 participants