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

Setting returnvalue to a substitute, previously configured in a AutoFixture.Register() call - throws CouldNotSetReturnDueToNoLastCallException #602

Closed
MagnusMikkelsen opened this issue Dec 16, 2019 · 4 comments

Comments

@MagnusMikkelsen
Copy link

Description
When setting the return value for a substitute method, where:

  • the metod has no arguments
  • the returnvalue also is a substitute
  • the returnvalue is configured in a AutoFixture.Register() call

the first Returns() method (foo.GiveMeBar().Returns(bar) in the example) throws a NSubstitute.Exceptions.CouldNotSetReturnDueToNoLastCallException.

It is possible to workaround the problem by either

  1. not configuring calls in the Register call block
  2. or by calling the method, that I want to set returnvalue for, before setting the returnvalue.

I have included these workarounds in the reproduction code as comments.

To Reproduce

using AutoFixture;
using AutoFixture.AutoNSubstitute;
using NSubstitute;

namespace ConsoleApp2
{
    public static class Program
    {
        public static void Main()
        {
            var f = new Fixture().Customize(new AutoNSubstituteCustomization() { ConfigureMembers = true });

            // Set default values for IBar
            f.Register<IBarRequest, IBar>(r =>
            {
                // Workaround 1:
                //Comment line below, and error goes away
                r.DefaultTrue.Returns(true);

                return (IBar)r;
            });

            // Create substitutes
            var foo = f.Create<IFoo>();
            var bar = f.Create<IBar>();


            // Doesn't throw:
            foo
                .GiveMeBarFromNumber(Arg.Any<int>())
                .Returns(bar);

            // Workaround 2:
            // Un-comment line below and error goes away
            // foo.GiveMeBar();

            // This Returns call throws CouldNotSetReturnDueToNoLastCallException
            foo
                .GiveMeBar()
                .Returns(bar);
        }
    }

    public interface IFoo
    {
        IBar GiveMeBar();
        IBar GiveMeBarFromNumber(int number);
    }

    public interface IBar
    {
        bool DefaultTrue { get; }
    }

    public interface IBarRequest : IBar { }
}

Expected behaviour
I did not expect any exception, because I called Returns() after calling my substitute, and I am not configuring other substitutes within Returns().

Environment:

  • NSubstitute version: 2.0.3
  • NSubstitute.Analyzers version: CSharp 1.0.11
  • AutoFixture 4.11.0
  • Platform: dotnetcore3.1 on Win. Also seen in .NET framework 4.8
@dtchepak
Copy link
Member

Thanks for the excellent repro case. 👍

Confirmed this behaviour on Mac, dotnetcore 2.0, NSub 4.2.1, AutoFixture 4.11.0.

I'm not sure exactly why this is happening from a quick look, but I think it is related to nested substitute configuration. Setting IBar.DefaultTrue while configuring the IFoo call makes NSubstitute think the call configuration is done while it is actually still configuring another call. (Similar to #217, #56.)

For the foo.GiveMeBarFromNumber(Arg.Any<int>()) call, the use of the arg matcher puts the substitute in a different, configuration mode. I need to look into why this fixes the problem, but for now you can try working around the issue by using NSubstitute.Extensions; and calling Configure() to put the substitute into that configuration mode for the GiveMeBar call as well:

            foo.Configure()
                .GiveMeBar()
                .Returns(bar);

Hope this helps get your test working again until I can get a better answer for you.

@zvirja
Copy link
Contributor

zvirja commented Jan 1, 2020

Thanks @MagnusMikkelsen for the nicely crafted scenario and @dtchepak for the brilliant analysis you did. The reasoning is exactly as described above. Upon receiving a call, NSubstitute remembers it as a "last" call, so that it could be configured by a subsequent call. In your case, the sequence is following:

  • GiveMeBar() call is received and stored as last
  • r.DefaultTrue getter call is received and stored as last
  • GiveMeBar() method returns
  • Returns(bar) tries to configure last call, which is wrong in our case.

This is an architectural issue which is quite hard to solve. That's why we introduced the Configure() method to provide NSubstitute with a hint that you actually don't use value returned from the call, as you are going to configure it. This way AutoFixture will not kick in, Register() callback will be not invoked and last call info will be not rewritten.

It's very unpleasant side-effect on AutoFixture-NSubstitute boundary, but hopefully workaround helps to mitigate it.

@MagnusMikkelsen
Copy link
Author

Thanks for the explanation @dtchepak and @zvirja, and thank you for your hard work on this library, it's fantastic 👍

I guess I'll just use the .Configure() workaround.

@zvirja
Copy link
Contributor

zvirja commented Jan 2, 2020

@MagnusMikkelsen Thanks! Let us know if you still need our assistance.

@zvirja zvirja closed this as completed Jan 2, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants