NSubstitute
NSubstitute copied to clipboard
Testing default interface methods in a mocked interface
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();
}
}
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.
Hi @ursmeili , Sorry, this is not implemented. Do you have any time to attempt a PR?
@dtchepak sorry, no, I have no spare time currently.
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...
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. :bow:
@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.
@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.