nsubstitute / NSubstitute

A friendly substitute for .NET mocking libraries.
https://nsubstitute.github.io
Other
2.67k stars 263 forks source link

Implicitly implement substituted interfaces #250

Closed jcansdale closed 7 years ago

jcansdale commented 8 years ago

Hello,

Is there any way to implicitly implement the methods of substituted interfaces?

For example the following test current fails. Is there any way to make it pass without duplicating the Returns?

        [Test]
        public void ImplicitlyImplemented()
        {
            var mysp = Substitute.For<MyServiceProvider, IServiceProvider>();

            mysp.GetService(null).Returns("mysp");

            var isp = (IServiceProvider)mysp;
            Assert.That(isp.GetService(null), Is.EqualTo(mysp.GetService(null)));
        }

        public interface MyServiceProvider
        {
            object GetService(Type serviceType);
        }

It looks like the substituted class will look something like this:

        public class ServiceProvider : IServiceProvider, MyServiceProvider
        {
            object MyServiceProvider.GetService(Type serviceType) { ... }
            object IServiceProvider.GetService(Type serviceType) { ... }
        }

Unfortunately this make the substituting of some COM interfaces particularly awkward.

dtchepak commented 8 years ago

Hi @jcansdale,

If these are the only two methods on the interface (or the only two that return object) you could try the ReturnsForAll extension. Bit of a hack but might work. Other than that, as far as NSubstitute knows these are two completely different methods so they'd generally need to be stubbed separately.

jcansdale commented 8 years ago

Arg, that's a shame. I'm substituting some pretty big and complex interfaces. 😢

The trick I was using with TypeMock was to create an interface that implements all of my target interfaces and mock that. Something like this:

using EnvDTE;
public interface MyCodeClass : CodeClass, CodeElement, CodeType
{
}

Alas this doesn't work with NSubstitute and CodeClass, CodeElement, CodeType end up with separate implementations.

Apart from this the conversion has gone very smoothly! 😄

dtchepak commented 8 years ago

Sorry the final mile hasn't been that smooth, but happy to hear things went pretty well in general. :)

If you need to do a lot of this you could try hacking something up with reflection. For all members on IServiceProvider, delegate matching calls on ServiceProvider to that instance:

// Pseudocode
TTarget sub = ...
TDelegator delegator = ... 
foreach member of typeof(TTarget):
   if delegator has matching member and non-void:
     invoke member on delegator
     SubstituteExtensions.Returns(delegator, x => invoke member on sub)
  else if delegator has matching member:
    wire up to When..Do

This would be pretty terrible for test performance I'm guessing, but I think it would work.

I'm not sure how to support this feature properly from NSubstitute. :-\ I'm open to any ideas :)

jcansdale commented 8 years ago

@dtchepak,

Thanks for your reply. I just had another look to see what I could come up with. I've stumbled across a workaround that, while not pretty, isn't error prone like what I was doing before (defining returns individually for each interface).

If I define a new abstract class like this:

        public abstract class ACodeClass : CodeClass, CodeElement, CodeType
        {
        }

Ctrl-. offers an option that I'd never seen before: "Implement interface abstractly". If I do this and then substitute for this abstract class, the interfaces will be implemented implicitly. Although verbose, this works out fine. :smile:

implement_interface_abstractly

Ideally this behavior would be the default when doing Substitute.For. Although I've know about explicitly implemented interfaces for a long time, I'm not sure I've ever seen them actually used in the wild. If it's tricky or potentially a breaking change, maybe the above workaround could be put in the FAQ? Substituting COM interfaces is a pretty great use-case and I expect this situation/gotcha is pretty common.

Thanks again for your work on NSubstitute. It really has been a pleasure to use. 😄

BTW, I've been working on a library for mocking static methods, that has ended up integrating perfectly with NSubstitute. If you're interested, I've put a simple getting started page up here: https://github.com/jcansdale/staticmocks

dtchepak commented 7 years ago

Closing as part of a general cleanup of issues. Please re-open if any action is required.