Scope modifiers in properties defined in interfaces cannot be used with A.Fake<>()
This is a valid interface in C#. However, using this interface to create a fake in a unit test existing in a different project will fail. This seems like a fringe case but perhaps this could be added to the limitations section. Not sure if it's fixable, or even worth fixing.
public interface TestInterface { string GetName { get; internal set; } }
Hi @AdamPliska,
To be honest, I'm surprised that this property declaration is legal. Normally, interface members are always public (although I guess it's no longer true with default interface methods). And it seems a bit strange to have an internal member in an interface, because it means that it can't be implemented in other assemblies. I can think of a few cases where it could be useful, though...
Anyway, the problem is that FakeItEasy can't implement a member it has no access to. A possible workaround would be to make the assembly's internals visible to DynamicProxy, as explained here.
Alternatively, if you need the setter to be accessible only in the same assembly as the interface, you could split the interface in 2:
public interface TestInterface { string GetName { get; } }
internal interface TestInterfaceSetter { string GetName { set; } }
And make the class that implements TestInterface also implement TestInterfaceSetter. In your test, just fake the public interface (you don't need to fake the setter, since you can configure what the getter returns)
OK, as I thought, this declaration is only legal since C# 8.0 (and I think it doesn't make much sense). What's really weird is that, even in the same assembly, I can't implement it!
// error CS0535: 'Test' does not implement interface member 'TestInterface.GetName.set'
class Test : TestInterface
{
public string GetName { get; internal set; }
}
If I make the setter public in the implementation, I get the same error.
The only thing that works is to implement the interface explicitly:
class Test : TestInterface
{
string TestInterface.GetName { get; set; }
}
Note that the internal modifier is not present in the implementation; trying to add it causes an error (error CS0106: The modifier 'internal' is not valid for this item).
So... I don't really understand how this is supposed to work. The only thing that I know for sure is that it's weird!
I do agree this is weird. The concept of an interface has changed dramatically with the latest c#. Scope, member implementations, lots of stuff that one would typically expect in a full class. I think that personal opinion aside, these features are quite niche. (lol, funny I give a personal opinion right after I say "personal opinions aside"...)
I feel a valid solution to this would be to add docs to the https://fakeiteasy.readthedocs.io/en/stable/what-can-be-faked/ page indicating what new interface features are not supported, and then if possible detect this situation and include necessary information in the error that indicates what is happening when the system breaks.
That's my two cents. I am very torn on even using these new interface features, but, opinions aside, this is where we find ourselves with c# and some folks will use these features.
Thanks for raising this, @AdamPliska. To make sure I'm seeing the same thing, even from within the same project, I'm seeing
public interface TestInterface
{
string GetName
{
get; internal set;
}
}
[Fact]
public void An_interface_with_internal_setter_can_be_faked()
{
A.Fake<TestInterface>();
}
yield
[xUnit.net 00:00:00.80] FakeItEasyQuestions.Test.An_interface_with_internal_setter_can_be_faked [FAIL]
X FakeItEasyQuestions.Test.An_interface_with_internal_setter_can_be_faked [147ms]
Error Message:
FakeItEasy.Core.FakeCreationException :
Failed to create fake of type FakeItEasyQuestions.Test+TestInterface:
No usable default constructor was found on the type FakeItEasyQuestions.Test+TestInterface.
An exception of type System.TypeLoadException was caught during this call. Its message was:
Method 'set_GetName' in type 'Castle.Proxies.ObjectProxy' from assembly 'DynamicProxyGenAssembly2, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null' does not have an implementation.
at System.Reflection.Emit.TypeBuilder.CreateTypeNoLock()
at System.Reflection.Emit.TypeBuilder.CreateTypeInfo()
at Castle.DynamicProxy.Generators.Emitters.AbstractTypeEmitter.CreateType(TypeBuilder type)
at Castle.DynamicProxy.Generators.Emitters.AbstractTypeEmitter.BuildType()
at Castle.DynamicProxy.Generators.ClassProxyGenerator.GenerateType(String name, Type[] interfaces, INamingScope namingScope)
at Castle.DynamicProxy.Generators.ClassProxyGenerator.<>c__DisplayClass1_0.<GenerateCode>b__0(String n, INamingScope s)
at Castle.DynamicProxy.Generators.BaseProxyGenerator.ObtainProxyType(CacheKey cacheKey, Func`3 factory)
at Castle.DynamicProxy.Generators.ClassProxyGenerator.GenerateCode(Type[] interfaces, ProxyGenerationOptions options)
at Castle.DynamicProxy.DefaultProxyBuilder.CreateClassProxyType(Type classToProxy, Type[] additionalInterfacesToProxy, ProxyGenerationOptions options)
at Castle.DynamicProxy.ProxyGenerator.CreateClassProxyType(Type classToProxy, Type[] additionalInterfacesToProxy, ProxyGenerationOptions options)
at Castle.DynamicProxy.ProxyGenerator.CreateClassProxy(Type classToProxy, Type[] additionalInterfacesToProxy, ProxyGenerationOptions options, Object[] constructorArguments, IInterceptor[] interceptors)
at FakeItEasy.Creation.CastleDynamicProxy.CastleDynamicProxyGenerator.GenerateInterfaceProxy(Type typeOfProxy, ReadOnlyCollection`1 additionalInterfacesToImplement, IEnumerable`1 attributes, IFakeCallProcessorProvider fakeCallProcessorProvider) in C:\projects\fakeiteasy\src\FakeItEasy\Creation\CastleDynamicProxy\CastleDynamicProxyGenerator.cs:line 43
Stack Trace:
at FakeItEasy.Creation.CreationResult.FailedCreationResult.get_Result() in C:\projects\fakeiteasy\src\FakeItEasy\Creation\CreationResult.cs:line 82
at FakeItEasy.Creation.FakeAndDummyManager.CreateFake(Type typeOfFake, LoopDetectingResolutionContext resolutionContext) in C:\projects\fakeiteasy\src\FakeItEasy\Creation\FakeAndDummyManager.cs:line 36
at FakeItEasy.A.Fake[T]() in C:\projects\fakeiteasy\src\FakeItEasy\A.cs:line 32
at FakeItEasyQuestions.Test.An_interface_with_internal_setter_can_be_faked() in D:\Sandbox\FakeItEasyQuestions\Test.cs:line 20
Does this match your experience?
(Oh, and on a hunch, I upgraded Castle.Core to 4.4.1, hoping it'd help. It did not.)
There's been no activity in this issue for some time, so we're closing it. We can reopen the issue in the future should sufficient interest arise.