-
Notifications
You must be signed in to change notification settings - Fork 255
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
AmbiguousArgumentsException when upgrading to 4.0 #499
Comments
Hi @waynebrantley ,
Notice that there is an Take a quick look around for The silver lining to this is that in future you'll be able to pick up these cases much more quickly and easily as NSubstitute 4.0 will report these sooner. Superfluous argument matchers can cause big problems for tests, so it's important that we start to throw errors in these cases. |
the test does this: That should be ok, shouldn't it?
|
Is |
No it is not. Reading the docs...again...it does say: Even without substituting for specific parts of a class, the instance returned by Substitute.ForPartsOf records all calls made to virtual members, so we can check Received() calls made to any partial substitute. So - it does not record calls to non-virtual members - so I cannot check these? Why does it work (and pass) in NSubstitute 2 and 3? |
Unfortunately NSubstitute cannot do anything with non-virtual members. The (approximate) way NSubstitute works is to dynamically create a sub-class (or a new class that implements an interface) of the type being substituted for. So we get something like this: public class Sample {
public virtual int DoStuffWith(string s) { return s.Length; }
}
// Substitute.For<Sample> returns an instance of:
class SampleProxy : Sample {
public override int DoStuffWith(string s) {
// record the call
// look up configured return value, run when..do actions
return configured_return_value;
}
} When the member is non-virtual, NSubstitute cannot override it, so it ends up being effectively invisible to NSubstitute. The reason it passes with NSubstitute 2 and 3 is because of this invisibility. For example:
Because (as far as NSub is concerned) we haven't said what call we want to check has been received, the assertion will never get triggered and therefore will never fail. This problem with virtual members has always been the significant downside of the syntax we have selected. But the concise syntax is nice, so we've thought it worth the cost. In particular, if doing a test-first style approach, we end up testing the test as we write it. For example: write a test to assert a call was received, run it and watch it fail, then implement the code to pass the test. If the second steps passes unexpectedly, we've immediately found an issue with the test. (This was the main scenario NSub was written for. It also works fine with test-after approaches, if we tweak the test/code after the fact to ensure it fails/tests what we think it does). We've been steadily changing to detect more of these cases at an earlier stage to help avoid misleading tests, so the problem caused updating to NSub 4 in this case has been a good thing (although I'm very sorry for the inconvenience in the first place!). The culmination of these efforts has been @tpodolak's NSubstitute.Analyzers, which will pick out a large number of these problematic cases at compile time using Roslyn. I strongly recommend installing this package wherever people have NSubstitute installed. I'm really sorry for the previous NSub versions resulting in a misleading test and for the confusion caused when upgrading. Hopefully this has given a good explanation of why we decided to make this breaking change. Regards, |
Makes sense...while 'breaking change'....my prior code was actually broken...so I guess it is ok! |
Hi @waynebrantley I did a quick check of analyzers based on previous comments, and the usages of Received, When and WhenForAnyArgs against non-virtual members are marked as warnings Can you provide a full sample of code for which analyzers are not showing the warnings? |
So the difference is the CqrsUow implements an interface
|
@waynebrantley thanks for providing the snippet, now I know what is going on.
instead |
Yes, figured that
Cannot really do that as the value we are using comes from DI, etc.... No worries Now that I am through all this - I really do appreciate the fact that NSubstitute 4 gives errors in this case. Previously it just 'appeared' the test was working - when in fact it was not! So while figuring out why and such was a bit of pain - this is a better solution. FYI: The only use case not really handled would be if I had only had ONE test - it does happily pass each time! It is only when I create a 2nd substitute that it fails. Thanks for your help on this and all your work on a really great product! |
I attempted to upgrade to 4, but ran into the error below. I have a unit test that will pass if run by itself. If I simply copy the unit test and make another version of the same test, the first one ran passes, but the 2nd one always fails.
In the [setup] (run before each test), I create a substitute using
ForPartsOf
. That looks like this:var cqrsUow = Substitute.ForPartsOf<CqrsUow>(); cqrsUow.WhenForAnyArgs(x => x.ActuallyCommit(null, false)).DoNotCallBase();
When the 2nd test runs - the error happens when executing the
WhenForAnyArgs
line above.I tried to create a simple repo, but could not - simply calling the [setup] method twice does not cause it. Calling [setup] method after I run my command that uses the DI and the above substitution does cause the error...but I could not get something simple for reproducing.
The unit tests described above all pass in NSubstitute 2.x and 3.x without any issues. Any ideas?
The text was updated successfully, but these errors were encountered: