Argument matchers (Arg.Is
, Arg.Any
etc.) now use ref
returns which were introduced in C# 7.0. This lets NSubstitute have better support for working with out
and ref
arguments, but also means that test written using previous NSubstitute versions will now fail to compile with pre-C# 7 compilers.
Reason: Previous NSubstitute versions had quite limited support for out
and ref
arguments. Enhanced support for out
and ref
in C# 7 means we are likely to see these being used more frequently. As the vast majority of people using NSubstitute seem to be on C# 7+ (based on NuGet statistics), we've thought it best to make sure NSubstitute's default behaviour works for these cases.
Workaround: If at all possible please update to a recent version of your .NET compiler (C# 7+, VB 2017, VB 15.3+, F# 4.1+). This should provide support for ref
returns and make the code compile fine with the new Arg
methods. These shipped with Visual Studio 2017. Visual Studio for Mac 2017 and JetBrains Rider 2017 also support C# 7+.
If it is not possible for you to use a C# 7-compatible compiler, we have added a compatibility wrapper around Arg
called CompatArg
, which has all the same members as Arg
just without the ref
return type. If you replace Arg.
references with CompatArg.
in your project then you can continue to use the old compiler with NSubstitute 4.x. If you find CompatArg
name too verbose, you can create a wrapper within your project with a shorter name (e.g. ArgC
) that delegates to CompatArg
.
Unused argument matchers (Arg.Is
, Arg.Any
etc.) will now throw an exception. This normally occurs if an argument matcher was used with a non-virtual call, or with an object that is not a substitute. This may cause existing tests to fail if they were misusing argument matchers in a way that did not cause an obvious problem.
Reason: Previously these were ignored, which could cause confusing test failures in subsequent tests. Now these cases should be picked up earlier and make finding the problem easier. See #89 and #279 for examples.
Workaround: Follow the instructions in the exception to fix any instances of this problem. Note that the cause of this exception can be in a previously executed test.
Calls made with one or more argument matchers (Arg.Is
or Arg.Any
) will no longer return previously configured results. NSubstitute will assume the call is being configured and avoid running logic configured via previous Returns()
calls.
In most cases this should not affect existing tests, but there are some ambiguously nested configurations involving argument matchers that can start to fail after this change.
See #345 (nsubstitute#347) for discussion of this and an example of a test whose behaviour has changed.
Reason: This fixes a number of issues relating to overlapping configurations (#291, #225, #146, #177).
Workaround: Separate any nested configurations that start to fail after this change. If this is difficult for a specific case, create a GitHub issue with the details and we may be able to assist.
Removed NSubstitute.Core.Extensions.Zip
.
Reason: Zip is in NET40+ and NetStandard. Was formerly provided for NET35 compatibility.
Workaround: Use System.Linq.Enumerable.Zip
Removed obsolete NSubstitute.Experimental.Received.InOrder
. (#351)
Reason: previously obsoleted by NSubstitute.Received.InOrder
.
Workaround: Use NSubstitute.Received.InOrder
.
Signed v3.x package to fix libraries that work with a mix of NSubstitute verisons. See #324.
NOTE: unsigned. Fixed in 3.0.1. NOTE: Support for NET45 and NET46 restored in 3.1.0.
Dropped support for older .NET platforms (pre-.NET 4.6).
Reason: switching to .NET Standard. This provides support for more platforms and makes the project easier to maintain.
Workaround: Stay with 2.x versions for older .NET platforms, or migrate to a .NET Standard 1.3 compatible target such as .NET 4.6 or later. See compatibility matrix:
https://github.com/dotnet/standard/blob/master/docs/versions.md
Substitutes will now automatically return an empty IQueryable<T>
for
members that return that type. Tests previously relying on a
substitute IQueryable<T>
will no longer work properly.
Reason:
- Code that uses an
IQueryable<T>
can now run using the auto-subbed value without causing null pointer exceptions (see issue #67).
Fix:
-
Avoid mocking
IQueryable<T>
where possible -- configure members to return a realIQueryable<T>
instead. If a substitute is required, explicitly configure the call to return a substitute:sub.MyQueryable().Returns(Substitute.For<IQueryable<int>>());
Substitutes set up to throw exception for methods with return type Task
cause compilation to fail due to the call being ambiguous (CS0121).
"The call is ambiguous between the following methods or properties:
.Returns<Task<T>>
and .Returns<T>
"
Reason:
- To make it easier to stub async methods. See issue #189.
Fix:
-
Specify generic type argument explicitly. If Method() returns string:
Old:
sub.Method().Returns(x => { throw new Exception() });
New:
sub.Method().Returns<string>(x => { throw new Exception() });
Incorrect use of argument matchers outside of a member call, particularly within a
Returns()
, will now throw an exception (instead of causing unexpected behaviour
in other tests: see nsubstitute#149).
Reason:
- Prevent accidental incorrect use from causing hard-to-find errors in unrelated tests.
Fix:
-
Do not use argument matchers in Returns() or outside of where an argument is normally used.
Correct use:
sub.Method(Arg.Any<string>()).Returns("hi")
Incorrect use:
sub.Method().Returns(Arg.Any<string>())
Auto-substitute for pure virtual classes with at least one public static method, which means some methods and properties on substitutes that used to return null by default will now return a new substitute of that type.
Reason:
- eep consistency with the behaviour of other pure virtual classes.
Fix:
- Explicitly return null from methods and property getters when required for a test.
e.g.
sub.Method().Returns(x => null)
;
Moved Received.InOrder
feature from NSubstitute.Experimental
to main NSubstitute
namespace. Obsoleted original NSubstitute.Experimental.Received
.
This can result in ambiguous reference compiler errors and obsolete member compiler earnings.
Reason:
- Promoted experimental Received feature to core library.
Fix:
- Import
NSubstitute
namespace instead ofNSubstitute.Experimental
. (IfNSubstitute
is already imported just delete theusing NSubstitute.Experimental;
line from your fixtures.)
The base object methods (Equals
, GetHashCode
and ToString
) for substitute objects of classes that extend those methods now return the result of calling the actual implementation of those methods rather than the default value for the return type. This means that places where you relied on .Equals
returning false
, .ToString
returning null
and .GetHashCode
returning 0
because the actual
methods weren't called will now call the actual implementation.
Reason:
- Substitute objects of classes that overrode those methods that were used as parameters for setting up return values or checking received calls weren't able to be directly used within the call, e.g. instead of:
someObject.SomeCall(aSubstitute).Returns(1);
You previously needed to have:
someObject.SomeCall(Arg.Is<TypeBeingSubstituted>(a => a == aSubstitute)).Returns(1);
However, now you can use the former, which is much more terse and consistent with the way other Returns or Received calls work.
- This means that substitute objects will now always work like .NET objects rather than being inconsistent when the class being substituted overrode any of those base object methods.
Fix:
- There is no workaround to change the behaviour of .Equals, .GetHashCode or .ToString. If you have a use case to change the behaviour of these methods please lodge an issue at the NSubstitute Github site.
In rare cases the new Returns()
and ReturnsForAnyArgs()
overloads can cause compilation to fail due to the call being ambiguous (CS0121).
Reason:
- The new overloads allow a sequence of callbacks to be used for return values. A common example is return several values, then throwing an exception.
Fix:
- Remove the ambiguity by explicitly casting the arguments types or by using lambda syntax. e.g.
sub.Call().Returns(x => null, x => null)
;
Auto-substitute from substitutes of Func
delegates (following the same rules as auto-subbing for methods and properties). So the delegate returned from Substitute.For<Func<IFoo>>()
will return a substitute of IFoo
. This means some substitutes for delegates that used to return null will now return a new substitute.
Reason:
- Reduced setup when substituting for
Func
delegates, and consistency with behaviour for properties and methods.
Fix:
- Explicitly return null from substitute delegates when required for a test.
e.g.
subFunc().Returns(x => null)
;
Auto-substitute for pure virtual classes (in addition to interfaces and delegate types), which means some methods and properties on substitutes that used to return null by default will now return a new substitute of that type.
Reason:
- Cut down the code required to configure substitute members that return interface-like types (e.g. ASP.NET web abstractions like
HttpContextBase
) which are safe to create and proxy. - Safe classes are those with all their public methods and properties defined as virtual or abstract, and containing a default, parameterless constructor defined as public or protected.
Fix:
- Explicitly return null from methods and property getters when required for a test.
e.g.
sub.Method().Returns(x => null)
;
Raise.Event<TEventArgs>(...)
methods renamed to Raise.EventWith<TEventArgs()
Reason:
- The
Raise.Event<TEventArgs>()
signature would often conflict with the Raise.Event<THandler>()
method which is used to raise all types of events.Raise.Event<THandler>()
will now always work for any event type, whileRaise.EventWith<TEventArgs>()
can be used as a shortcut to raise- EventHandler-style events with a particular argument.
Fix:
- Replace
Raise.Event<TEventArgs>()
calls with equivalentRaise.EventWith<TEventArgs>()
call.
Raise.Action()
methods removed
Reason:
- The
Raise.Event<THandler>()
method can be used to raise all delegate events, including Actions. Raise.Action()
was removed so there is a consistent way of raising all delegate events.
Fix:
- Replace
Raise.Action()
calls withRaise.Event<Action>()
. - Replace
Raise.Action<T>(T arg)
calls withRaise.Event<Action<T>>(arg)
. - Replace
Raise.Action<T1,T2>(T1 x, T2 y)
calls withRaise.Event<Action<T1,T2>>(x, y)
.
No breaking changes.