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

Don't Force Expectations To Be Set For Members That Have Implementations #163

Closed
JasonBock opened this issue Apr 7, 2022 · 5 comments
Closed
Assignees
Milestone

Comments

@JasonBock
Copy link
Owner

Consider the following interface:

public interface IPolygon
{
  int NumberOfSides { get; }
  int SideLength { get; }

  double GetPerimeter() => this.SideLength * this.NumberOfSides;
}

If I do this:

var polygonMock = Rock.Create<IPolygon>();
polygonMock.Properties().Getters().SideLength().Returns(3);
polygonMock.Properties().Getters().NumberOfSides().Returns(5);

var polygon = polygonMock.Instance();
Console.WriteLine(polygon.GetPerimeter());

I get an ExpectedException.

If I change this to a class:

public class Polygon
{
  public virtual int NumberOfSides { get; }
  public virtual int SideLength { get; }

  public virtual double GetPerimeter() => this.SideLength * this.NumberOfSides;
}

Then, if I do this:

var polygonMock = Rock.Create<Polygon>();
polygonMock.Properties().Getters().SideLength().Returns(3);
polygonMock.Properties().Getters().NumberOfSides().Returns(5);

var polygon = polygonMock.Instance();
Console.WriteLine(polygon.GetPerimeter());

I still get an exception.

My point is, if a virtual member has an implementation, I shouldn't fail if an expectation is not in place. I can still create an expectation for it, but if it doesn't exist, just fall back to the implementation that is in place.

@JasonBock
Copy link
Owner Author

I can do this with a base class, but not a base interface with DIM. I need this feature to make its way into C# for Rocks to be able to call the "base" implementation.

@JasonBock
Copy link
Owner Author

JasonBock commented Apr 10, 2022

So, it looks like a type that wraps + delegates for non-DIMs and not implement the DIMs would work. However, that's also a fair amount of code I'd have to generate, so....I'm really not keen on doing that right now.

I think what I'll do is create another issue that says "we should allow base DIM calls" and then state that either we wait for the compiler+runtime support, or do this approach. At least now we won't be forcing users to implement expectations for virtual calls on base class members.

@JasonBock
Copy link
Owner Author

I changed my mind :). I think I'm going to put this shim code in now.

@JasonBock
Copy link
Owner Author

Giving credit where credit is due .... this is the thread post that suggested the wrapping/forwarding idea.

@JasonBock
Copy link
Owner Author

So the idea is this. As I'm finding mockable members in MockInformation, I'll also grab all the interfaces that have virtual members (i.e. implementations) in a hash set. Then, for each of those interfaces:

  • Create a readonly field in the mock object named something like shimInterfaceName (yes, this could end up with a name collision, but .... just ignore that for now, I could create the full name with no periods as well to get around that)
  • In every constructor, set the field equal to a shim object named RockShimInterfaceName, which will take a reference to the mock object - i.e. this
  • Whenever I run into a method that has a base implementation and it's an interface, make the call - this.shimInterfaceName.member...

The shim class is a private sealed class to the mock object. It has one constructor that takes a reference to the mock object. This shim class implements every non-virtual member and forwards the call to the wrapped mock object. Virtual members have nothing done for them.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant