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

Proxy methods calls to real implementation #435

Closed
marcofranssen opened this issue Jan 13, 2015 · 7 comments
Closed

Proxy methods calls to real implementation #435

marcofranssen opened this issue Jan 13, 2015 · 7 comments

Comments

@marcofranssen
Copy link

I'm trying to proxy calls to a fake object to the actual implementation. The reason for this is that I want to be able to use the WasToldTo and WhenToldTo of Machine.Specifications which only works on fakes of an interface type.

Therefore I'm doing the following to proxy all calls to my real object.

public static TFake Proxy<TFake, TInstance>(TFake fake, TInstance instance) where TInstance : TFake
{
    fake.Configure().AnyCall().Invokes(x => x.Method.Invoke(instance, x.Arguments.ToArray()));
    return fake;
}

I would use that like this.

 var fake = Proxy<ISomeInterface, SomeImplementation>(A.Fake<ISomeInterface>(), new SomeImplementation());

// in my assertions using Machine.Specifications (reason I need a fake of an interface)
fake.WasToldTo(x => x.DoOperation());

The problem however is that this only works for void methods, since the Invokes method is not doing anything with the return value. (Action param instead of Func<T, TReturn>)

Then I was trying to do this using the WithReturnValue method.

public static TFake Proxy<TFake, TInstance>(TFake fake, TInstance instance) where TInstance : TFake
{
    fake.Configure().AnyCall().WithReturnType<int>().Invokes(x => x.Method.Invoke(instance, x.Arguments.ToArray()));
    fake.Configure().AnyCall().WithReturnType<string>().Invokes(x => x.Method.Invoke(instance, x.Arguments.ToArray()));
    fake.Configure().AnyCall().WithReturnType<bool>().Invokes(x => x.Method.Invoke(instance, x.Arguments.ToArray()));
    //etc.
    return fake;
}

However the Invokes method still doesn't work the way I want it (still Action instead of Func). So The return value is still not used.

Is there a way of achieving this with the current latest version? Could we add a Proxy method to FakeItEasy in a future version?

For the latter it maybe would be cool to have it available something more fluent like.

fake.ProxyTo(new RealObject());
@blairconrad
Copy link
Member

I'd commented in error earlier. You can create a fake to wrap an existing object like so:

var wrapped = new FooClass("foo", "bar");
var foo = A.Fake<IFoo>(x => x.wrapping(wrapped));

(example taken from Creating Fake > Options)

That should delegate all calls to the underlying object, with the usual caveat that any redirected calls have to be overrideable.

I hope this helps. If not, come back and explain again. Maybe I'll understand it better.

Oh, and beware the Configure mechanism. It's going away in FakeItEasy 2.0.0.
The preferred idiom is

A.CallTo(fake).Invokes(); // or
A.CallTo(fake).WithReturnType<bool>(); 

@marcofranssen
Copy link
Author

Thanks for the answer. Replaced Configure with the A.Callto syntax. Since not all is overridable I used following approach to support all of my codepaths easily.

public static TFake Proxy<TFake, TInstance>(TFake fake, TInstance instance) where TInstance : TFake
{
    ProxyReturnType<TFake, TInstance, int>(fake, instance);
    ProxyReturnType<TFake, TInstance, bool>(fake, instance);
    ProxyReturnType<TFake, TInstance, string>(fake, instance);
    ProxyReturnType<TFake, TInstance, Contact>(fake, instance);
    ProxyReturnType<TFake, TInstance, Instance>(fake, instance);
    ProxyReturnType<TFake, TInstance, PostItem>(fake, instance);
    ProxyReturnType<TFake, TInstance, SpaceItem>(fake, instance);
    ProxyReturnType<TFake, TInstance, Notification>(fake, instance);
    ProxyReturnType<TFake, TInstance, Reaction>(fake, instance);
    ProxyReturnType<TFake, TInstance, GoodAnswer>(fake, instance);
    ProxyReturnType<TFake, TInstance, SmartList>(fake, instance);
    A.CallTo(fake).Where(x => x.Method.ReturnType == typeof(void)).Invokes(x => x.Method.Invoke(instance, x.Arguments.ToArray()));

    return fake;
}

private static void ProxyReturnType<TFake, TInstance, TMethodReturnType>(TFake fake, TInstance instance) where TInstance : TFake
{
    A.CallTo(fake).WithReturnType<TMethodReturnType>().ReturnsLazily(x => (TMethodReturnType)x.Method.Invoke(instance, x.Arguments.ToArray()));
    A.CallTo(fake).WithReturnType<IQueryable<TMethodReturnType>>().ReturnsLazily(x => (IQueryable<TMethodReturnType>)x.Method.Invoke(instance, x.Arguments.ToArray()));
    A.CallTo(fake).WithReturnType<IEnumerable<TMethodReturnType>>().ReturnsLazily(x => (IEnumerable<TMethodReturnType>)x.Method.Invoke(instance, x.Arguments.ToArray()));
    A.CallTo(fake).WithReturnType<ICollection<TMethodReturnType>>().ReturnsLazily(x => (ICollection<TMethodReturnType>)x.Method.Invoke(instance, x.Arguments.ToArray()));
    A.CallTo(fake).WithReturnType<IList<TMethodReturnType>>().ReturnsLazily(x => (IList<TMethodReturnType>)x.Method.Invoke(instance, x.Arguments.ToArray()));
    A.CallTo(fake).WithReturnType<List<TMethodReturnType>>().ReturnsLazily(x => (List<TMethodReturnType>)x.Method.Invoke(instance, x.Arguments.ToArray()));
    A.CallTo(fake).WithReturnType<Task<TMethodReturnType>>().ReturnsLazily(x => (Task<TMethodReturnType>)x.Method.Invoke(instance, x.Arguments.ToArray()));
    A.CallTo(fake).WithReturnType<Task<ICollection<TMethodReturnType>>>().ReturnsLazily(x => (Task<ICollection<TMethodReturnType>>)x.Method.Invoke(instance, x.Arguments.ToArray()));
}

public static TFake Proxy<TFake, TInstance>(TInstance instance) where TInstance : TFake
{
    var fake = A.Fake<TFake>();
    return Proxy(fake, instance);
}

It can be used as following

var actual = new SomeClass(); //Someclass implements ISomeInterface
return Proxy<ISomeInterface, SomeClass>(actual);

@blairconrad
Copy link
Member

@marcofranssen, I am not sure if you are content with your solution or not, although the A.CallTos look great. Is there a reason you're avoiding using FakeItEasy's built-in wrapping mechanism? I haven't run down all your possible return types, but I did implement my own SomeClass and ran a quick pair of tests, using your Proxy and the built-in wrappers, and both tests pass:

public class SomeClass : ISomeInterface
{
    public string GetName()
    {
        return "SomeClass";
    }
}

public interface ISomeInterface
{
    string GetName();
}

[Test]
public void Can_use_proxy_method_to_make_wrapping_fake()
{
    var actual = new SomeClass(); //Someclass implements ISomeInterface
    var proxy = Proxy<ISomeInterface, SomeClass>(actual);

    Assert.That(proxy.GetName(), Is.EqualTo("SomeClass"));
}

[Test]
public void Can_wrap_a_fake_using_builtin_mechanism()
{
    var actual = new SomeClass(); //Someclass implements ISomeInterface
    var proxy = A.Fake<ISomeInterface>(options => options.Wrapping(actual));

    Assert.That(proxy.GetName(), Is.EqualTo("SomeClass"));
}

Unless there's a failing in the built-in wrapper, it might be able to save you some aggravation, especially if your classes start working with more types.

@blairconrad
Copy link
Member

@marcofranssen Can we conclude that you're satisfied with your answer?

If everything's working great for you, we're happy, but the corollary to my last comment is that if there is a failing in the built-in wrapper, we'd be grateful to have it explained so we can try to rectify it, for everyone's benefit.

@marcofranssen
Copy link
Author

Somehow missed the wrapper method. Still need to test that one. Keep you posted. Thanks for the help so far.

@blairconrad
Copy link
Member

From @marcofranssen's comment and acceptance of my answer to the Stack Overflow question, it seems that Wrapping worked fine.
Closing.

@adamralph
Copy link
Contributor

Great!

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

3 participants