sbergen / GenSubstitute

GenSubstitute is a C# source generator based mocking library
MIT License
2 stars 0 forks source link

Support interfaces with members hidden with new #4

Open sbergen opened 2 years ago

sbergen commented 2 years ago

Interfaces that hide base members with new are currently not supported. However, I have not yet come up with a syntax to support them that I would be happy with.

Let's take a look at the following example:

interface IApproximateValue
{
    int Value { get; }
}

interface IMoreExactValue : IApproximateValue
{
    new double Value { get; }
}

The following code will currently trigger source generation that won't compile:

var sub = Gen.Substitute<IMoreExactValue>().Create();

Fixing the source generation for the implementation of sub.Object will be easy, simply by implementing all members explicitly.

However, the cases of sub.SetUp, sub.Received, and sub.Match will be more complicated.

E.g. in NSubstitute, you could do something like ((IApproximateValue)sub).Value.Returns(5);, but with the more explicit and "more statically typed" code in GenSubstitute, this is not possible. Let's consider some options:

  1. If we were to make the main substitute class implement a bunch of interfaces, the interface names would be pretty atrocious with the current model. The current names for a nested interface, for example, would be like: GenSubstitute.SomeNamespace_SomeClass_SomeNestedInterface_Substitute. Nobody wants to cast sub to that type :)
    • I'm not sure if the naming scheme could be modified somehow to make this more accessible.
  2. The ideal syntax would be something like sub.SetUp<IApproximateValue>()..., but I'm not aware of any trick that could be used to then denote the correct return type.
  3. The name of the interface could be included in the property name, like sub.SetUp_IApproximateValue..., but that would get messy with e.g. generic interfaces and if an interface inherits multiple interfaces with the same name, but different scope (e.g. namepsace).
  4. There could be a dictionary of base interfaces, like sub.SetUp[typeof(IApproximateValue)]..., but again, the result type can't be statically deduced.

If someone has read this far, and has any ideas, please share! I've spent quite some time thinking about this, but haven't yet come up with a solution I would be happy with.

sbergen commented 1 month ago

One option here would be to generate a bunch of Deriving(SomeAtrociousSubstituteName substitute) method overrides. This would allow configuring the base interfaces separately and making things more composable in general. However, how this affects non-hidden interface members needs to be explored.

E.g.

var approxSub = Gen.Substitute<IApproximateValue>().Create();
var sub = Gen.Substitute<IMoreExactValue>().Create().Deriving(approxSub);