Track last call and spec in thread-safe manner #264
Rename ICallStack to ICallCollection. That is because the Pop() method is not required more (and was deleted), and Delete() method was added instead. That is much more collection that stack. IPendingSpecification now holds both information about previous call and pending specification. That is encapsulated to the PendingSpecificationInfo. The reasons why I added one more responsibility to this class are following: - We always either use pending specification or information about previous call. This data is mutually exclusive (we delete other one when store current one). Therefore, I decided that information about previous call is also a specification to some degree. - It's simpler to track sources of specifications because now we have a single place only. In my previous implementation we needed to clear pending specification and information about last call - more chances to fail somewhere. - Data is stored in SubstitutionContext, so we have single field instead of two (one for pending specification, second for last call information). PendingSpecificationInfo stores thread-local on SubstitutionContext (i.e. per substitute). GetCallSpec now uses IPendingSpecification only to resolve specification. Therefore, I renamed the FromLastCall method to the FromPendingSpecification because it better reflects the method implementation. TrackLastCallHandler introduced to set `IPendingSpecification`.
This commit is contained in:
Родитель
ed904cabd7
Коммит
bb06d33338
|
@ -15,4 +15,5 @@ _ReSharper*
|
|||
.DS_Store
|
||||
project.lock.json
|
||||
.vs/
|
||||
.fake/
|
||||
.fake/
|
||||
.idea/
|
|
@ -112,6 +112,9 @@
|
|||
<Compile Include="..\NSubstitute.Acceptance.Specs\ClearSubstitute.cs">
|
||||
<Link>ClearSubstitute.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\NSubstitute.Acceptance.Specs\ConcurrencyTests.cs">
|
||||
<Link>ConcurrencyTests.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\NSubstitute.Acceptance.Specs\CustomHandlersSpecs.cs">
|
||||
<Link>CustomHandlersSpecs.cs</Link>
|
||||
</Compile>
|
||||
|
|
|
@ -0,0 +1,123 @@
|
|||
using System;
|
||||
using System.Threading;
|
||||
using NSubstitute.Acceptance.Specs.Infrastructure;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace NSubstitute.Acceptance.Specs
|
||||
{
|
||||
public class ConcurrencyTests
|
||||
{
|
||||
[Test]
|
||||
public void Call_between_invocation_and_received_doesnt_cause_issue()
|
||||
{
|
||||
//arrange
|
||||
var subs = Substitute.For<ISomething>();
|
||||
|
||||
var backgroundReady = new AutoResetEvent(false);
|
||||
|
||||
//act
|
||||
var dummy = subs.Say("ping");
|
||||
|
||||
RunInOtherThread(() =>
|
||||
{
|
||||
subs.Echo(42);
|
||||
backgroundReady.Set();
|
||||
});
|
||||
|
||||
backgroundReady.WaitOne();
|
||||
|
||||
dummy.Returns("pong");
|
||||
|
||||
//assert
|
||||
var actualResult = subs.Say("ping");
|
||||
|
||||
Assert.That(actualResult, Is.EqualTo("pong"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Background_invocation_doesnt_delete_specification()
|
||||
{
|
||||
//arrange
|
||||
var subs = Substitute.For<ISomething>();
|
||||
|
||||
var backgroundReady = new AutoResetEvent(false);
|
||||
|
||||
//act
|
||||
var dummy = subs.Say(Arg.Any<string>());
|
||||
|
||||
RunInOtherThread(() =>
|
||||
{
|
||||
subs.Say("hello");
|
||||
backgroundReady.Set();
|
||||
});
|
||||
|
||||
backgroundReady.WaitOne();
|
||||
dummy.Returns("42");
|
||||
|
||||
//assert
|
||||
Assert.That(subs.Say("Alex"), Is.EqualTo("42"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Both_threads_can_configure_returns_concurrently()
|
||||
{
|
||||
//arrange
|
||||
var subs = Substitute.For<ISomething>();
|
||||
|
||||
var foregroundReady = new AutoResetEvent(false);
|
||||
var backgroundReady = new AutoResetEvent(false);
|
||||
|
||||
//act
|
||||
//1
|
||||
var dummy = subs.Say("ping");
|
||||
|
||||
RunInOtherThread(() =>
|
||||
{
|
||||
//2
|
||||
var d = subs.Echo(42);
|
||||
SignalAndWait(backgroundReady, foregroundReady);
|
||||
|
||||
//4
|
||||
d.Returns("42");
|
||||
backgroundReady.Set();
|
||||
});
|
||||
|
||||
backgroundReady.WaitOne();
|
||||
|
||||
//3
|
||||
dummy.Returns("pong");
|
||||
SignalAndWait(foregroundReady, backgroundReady);
|
||||
|
||||
//assert
|
||||
Assert.That(subs.Say("ping"), Is.EqualTo("pong"));
|
||||
Assert.That(subs.Echo(42), Is.EqualTo("42"));
|
||||
}
|
||||
|
||||
#if (NET45 || NET4 || NETSTANDARD1_5)
|
||||
[Test]
|
||||
public void Configuration_works_fine_for_async_methods()
|
||||
{
|
||||
//arrange
|
||||
var subs = Substitute.For<ISomething>();
|
||||
|
||||
//act
|
||||
subs.EchoAsync(42).Returns("42");
|
||||
|
||||
//assert
|
||||
var result = subs.EchoAsync(42).Result;
|
||||
Assert.That(result, Is.EqualTo("42"));
|
||||
}
|
||||
#endif
|
||||
|
||||
private static void RunInOtherThread(Action action)
|
||||
{
|
||||
new Thread(action.Invoke) {IsBackground = true}.Start();
|
||||
}
|
||||
|
||||
private static void SignalAndWait(EventWaitHandle toSignal, EventWaitHandle toWait)
|
||||
{
|
||||
toSignal.Set();
|
||||
toWait.WaitOne();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -11,9 +11,11 @@
|
|||
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath>
|
||||
<OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Service Include="{82a7f48d-3b50-4b1e-b82e-3ada8210c358}" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.targets" Condition="'$(VSToolsPath)' != ''" />
|
||||
</Project>
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace NSubstitute.Acceptance.Specs
|
||||
|
@ -54,7 +55,39 @@ namespace NSubstitute.Acceptance.Specs
|
|||
Console.WriteLine("{0}", watch.ElapsedMilliseconds);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[Ignore("It's a stress test. It might take a lot of time and it not optimized for frequent execution.")]
|
||||
public void Multiple_return_configuration_dont_leak_memory_for_any_args()
|
||||
{
|
||||
const int bufferSize = 100000000; //100 MB
|
||||
|
||||
var subs = Substitute.For<IByteArrayConsumer>();
|
||||
|
||||
//1000 chunks each 100 MB will require 100GB. If leak is present - OOM should be thrown.
|
||||
for (var i = 0; i < 1000; i++)
|
||||
{
|
||||
subs.ConsumeArray(new byte[bufferSize]).ReturnsForAnyArgs(true);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
[Ignore("FAILS because of CallResults leak")]
|
||||
public void Muiltiple_return_configurations_dont_lead_to_memory_leak()
|
||||
{
|
||||
const int bufferSize = 100000000; //100 MB
|
||||
|
||||
var subs = Substitute.For<IByteArraySource>();
|
||||
|
||||
//1000 chunks each 100 MB will require 100GB. If leak is present - OOM should be thrown.
|
||||
for (int i = 0; i < 1000; i++)
|
||||
{
|
||||
subs.GetArray().Returns(new byte[bufferSize]);
|
||||
}
|
||||
}
|
||||
|
||||
public interface IFoo { int GetInt(string s); }
|
||||
public interface IBar { int GetInt<T>(T t); }
|
||||
public interface IByteArraySource { byte[] GetArray(); }
|
||||
public interface IByteArrayConsumer { bool ConsumeArray(byte[] array); }
|
||||
}
|
||||
}
|
|
@ -249,6 +249,9 @@
|
|||
<Compile Include="..\NSubstitute\Core\CallBaseExclusions.cs">
|
||||
<Link>Core\CallBaseExclusions.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\NSubstitute\Core\CallCollection.cs">
|
||||
<Link>Core\CallCollection.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\NSubstitute\Core\CallFactory.cs">
|
||||
<Link>Core\CallFactory.cs</Link>
|
||||
</Compile>
|
||||
|
@ -285,9 +288,6 @@
|
|||
<Compile Include="..\NSubstitute\Core\CallSpecificationFactoryFactoryYesThatsRight.cs">
|
||||
<Link>Core\CallSpecificationFactoryFactoryYesThatsRight.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\NSubstitute\Core\CallStack.cs">
|
||||
<Link>Core\CallStack.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\NSubstitute\Core\ConfigureCall.cs">
|
||||
<Link>Core\ConfigureCall.cs</Link>
|
||||
</Compile>
|
||||
|
@ -330,6 +330,9 @@
|
|||
<Compile Include="..\NSubstitute\Core\ICallBaseExclusions.cs">
|
||||
<Link>Core\ICallBaseExclusions.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\NSubstitute\Core\ICallCollection.cs">
|
||||
<Link>Core\ICallCollection.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\NSubstitute\Core\ICallHandler.cs">
|
||||
<Link>Core\ICallHandler.cs</Link>
|
||||
</Compile>
|
||||
|
@ -357,9 +360,6 @@
|
|||
<Compile Include="..\NSubstitute\Core\ICallSpecificationFactory.cs">
|
||||
<Link>Core\ICallSpecificationFactory.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\NSubstitute\Core\ICallStack.cs">
|
||||
<Link>Core\ICallStack.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\NSubstitute\Core\IConfigureCall.cs">
|
||||
<Link>Core\IConfigureCall.cs</Link>
|
||||
</Compile>
|
||||
|
@ -435,6 +435,9 @@
|
|||
<Compile Include="..\NSubstitute\Core\PendingSpecification.cs">
|
||||
<Link>Core\PendingSpecification.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\NSubstitute\Core\PendingSpecificationInfo.cs">
|
||||
<Link>Core\PendingSpecificationInfo.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\NSubstitute\Core\PropertyCallFormatter.cs">
|
||||
<Link>Core\PropertyCallFormatter.cs</Link>
|
||||
</Compile>
|
||||
|
@ -666,6 +669,9 @@
|
|||
<Compile Include="..\NSubstitute\Routing\Handlers\SetActionForCallHandler.cs">
|
||||
<Link>Routing\Handlers\SetActionForCallHandler.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\NSubstitute\Routing\Handlers\TrackLastCallHandler.cs">
|
||||
<Link>Routing\Handlers\TrackLastCallHandler.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\NSubstitute\Routing\IRoute.cs">
|
||||
<Link>Routing\IRoute.cs</Link>
|
||||
</Compile>
|
||||
|
@ -689,7 +695,6 @@
|
|||
</None>
|
||||
<None Include="ilmerge.exclude" />
|
||||
</ItemGroup>
|
||||
<ItemGroup />
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" Condition="($(MSBuildTargets) == '') Or ($(MSBuildTargets) == 'CSharp')" />
|
||||
<Target Name="AfterBuild" Condition="(($(MSBuildTargets) == '') Or ($(MSBuildTargets) == 'CSharp')) And '$(OS)' == 'Windows_NT'">
|
||||
<CreateItem Include="@(ReferenceCopyLocalPaths)" Condition="'%(Extension)'=='.dll'">
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
using System;
|
||||
using NSubstitute.Core;
|
||||
using NSubstitute.Specs.Infrastructure;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace NSubstitute.Specs
|
||||
{
|
||||
public class CallCollectionSpecs : ConcernFor<CallCollection>
|
||||
{
|
||||
public override CallCollection CreateSubjectUnderTest() => new CallCollection();
|
||||
|
||||
[Test]
|
||||
public void Should_add_call()
|
||||
{
|
||||
//arrange
|
||||
var call = mock<ICall>();
|
||||
|
||||
//act
|
||||
sut.Add(call);
|
||||
|
||||
//assert
|
||||
CollectionAssert.Contains(sut.AllCalls(), call);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Should_delete_call_when_deleted()
|
||||
{
|
||||
//arrange
|
||||
var call = mock<ICall>();
|
||||
|
||||
//act
|
||||
sut.Add(call);
|
||||
sut.Delete(call);
|
||||
|
||||
//assert
|
||||
CollectionAssert.DoesNotContain(sut.AllCalls(), call);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Should_fail_when_delete_nonexisting_call()
|
||||
{
|
||||
//arrange
|
||||
var call = mock<ICall>();
|
||||
|
||||
//act/assert
|
||||
var exception = Assert.Throws<InvalidOperationException>(() => sut.Delete(call));
|
||||
Assert.That(exception.Message, Is.StringContaining("Collection doesn't contain the call."));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Should_delete_all_calls_on_clear()
|
||||
{
|
||||
//arrange
|
||||
var call1 = mock<ICall>();
|
||||
var call2 = mock<ICall>();
|
||||
|
||||
//act
|
||||
sut.Add(call1);
|
||||
sut.Add(call2);
|
||||
|
||||
sut.Clear();
|
||||
|
||||
//assert
|
||||
CollectionAssert.IsEmpty(sut.AllCalls());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Should_return_all_calls_in_the_order_they_were_received()
|
||||
{
|
||||
//arrange
|
||||
var firstCall = mock<ICall>();
|
||||
var secondCall = mock<ICall>();
|
||||
|
||||
//act
|
||||
sut.Add(firstCall);
|
||||
sut.Add(secondCall);
|
||||
|
||||
//assert
|
||||
CollectionAssert.AreEqual(sut.AllCalls(), new[] { firstCall, secondCall });
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,94 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NSubstitute.Core;
|
||||
using NSubstitute.Specs.Infrastructure;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace NSubstitute.Specs
|
||||
{
|
||||
public class CallStackSpecs
|
||||
{
|
||||
public abstract class Concern : ConcernFor<CallStack>
|
||||
{
|
||||
public override CallStack CreateSubjectUnderTest()
|
||||
{
|
||||
return new CallStack();
|
||||
}
|
||||
}
|
||||
|
||||
public class When_a_call_has_been_pushed : Concern
|
||||
{
|
||||
ICall _call;
|
||||
|
||||
[Test]
|
||||
public void Should_pop_to_get_that_call_back()
|
||||
{
|
||||
Assert.That(sut.Pop(), Is.SameAs(_call));
|
||||
}
|
||||
|
||||
public override void Because()
|
||||
{
|
||||
sut.Push(_call);
|
||||
}
|
||||
|
||||
public override void Context()
|
||||
{
|
||||
base.Context();
|
||||
_call = mock<ICall>();
|
||||
}
|
||||
}
|
||||
|
||||
public class When_the_call_stack_is_empty : Concern
|
||||
{
|
||||
[Test]
|
||||
public void Should_get_a_stack_empty_exception_when_trying_to_pop()
|
||||
{
|
||||
var exception = Assert.Throws<InvalidOperationException>(() => sut.Pop());
|
||||
Assert.That(exception.Message, Is.StringContaining("Stack empty"));
|
||||
}
|
||||
}
|
||||
|
||||
public class When_calls_are_cleared : Concern
|
||||
{
|
||||
[Test]
|
||||
public void Should_not_have_any_calls_to_pop()
|
||||
{
|
||||
Assert.Throws<InvalidOperationException>(() => sut.Pop());
|
||||
}
|
||||
|
||||
public override void Because()
|
||||
{
|
||||
sut.Push(mock<ICall>());
|
||||
sut.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
public class When_finding_all_calls : Concern
|
||||
{
|
||||
private IEnumerable<ICall> _result;
|
||||
private ICall _firstCall;
|
||||
private ICall _secondCall;
|
||||
|
||||
[Test]
|
||||
public void Should_return_all_calls_in_the_order_they_were_received()
|
||||
{
|
||||
Assert.That(_result.ToArray(), Is.EqualTo(new[] { _firstCall, _secondCall }));
|
||||
}
|
||||
|
||||
public override void Because()
|
||||
{
|
||||
sut.Push(_firstCall);
|
||||
sut.Push(_secondCall);
|
||||
_result = sut.AllCalls();
|
||||
}
|
||||
|
||||
public override void Context()
|
||||
{
|
||||
base.Context();
|
||||
_firstCall = mock<ICall>();
|
||||
_secondCall = mock<ICall>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -37,7 +37,7 @@ namespace NSubstitute.Specs
|
|||
public void Configure_result_for_last_specified_call()
|
||||
{
|
||||
var lastCallSpec = mock<ICallSpecification>();
|
||||
_getCallSpec.stub(x => x.FromLastCall(MatchArgs.AsSpecifiedInCall)).Return(lastCallSpec);
|
||||
_getCallSpec.stub(x => x.FromPendingSpecification(MatchArgs.AsSpecifiedInCall)).Return(lastCallSpec);
|
||||
|
||||
sut.SetResultForLastCall(_compatibleReturnValue, MatchArgs.AsSpecifiedInCall);
|
||||
|
||||
|
@ -68,7 +68,7 @@ namespace NSubstitute.Specs
|
|||
Action<CallInfo> firstAction = x => { };
|
||||
Action<CallInfo> secondAction = x => { };
|
||||
var lastCallSpec = mock<ICallSpecification>();
|
||||
_getCallSpec.stub(x => x.FromLastCall(MatchArgs.AsSpecifiedInCall)).Return(lastCallSpec);
|
||||
_getCallSpec.stub(x => x.FromPendingSpecification(MatchArgs.AsSpecifiedInCall)).Return(lastCallSpec);
|
||||
|
||||
var config = sut.SetResultForLastCall(_compatibleReturnValue, MatchArgs.AsSpecifiedInCall);
|
||||
config
|
||||
|
@ -86,7 +86,7 @@ namespace NSubstitute.Specs
|
|||
public void Configure_result_for_last_specified_call()
|
||||
{
|
||||
var lastCallSpec = mock<ICallSpecification>();
|
||||
_getCallSpec.stub(x => x.FromLastCall(MatchArgs.AsSpecifiedInCall)).Return(lastCallSpec);
|
||||
_getCallSpec.stub(x => x.FromPendingSpecification(MatchArgs.AsSpecifiedInCall)).Return(lastCallSpec);
|
||||
lastCallSpec.stub(x => x.GetMethodInfo()).Return(ReflectionHelper.GetMethod(() => SomeType.SampleMethod()));
|
||||
|
||||
var incompatibleReturn = mock<IReturn>();
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
using NSubstitute.Core;
|
||||
using System;
|
||||
using NSubstitute.Core;
|
||||
using NSubstitute.ReturnsExtensions;
|
||||
using NSubstitute.Specs.Infrastructure;
|
||||
using NUnit.Framework;
|
||||
|
||||
|
@ -8,14 +10,14 @@ namespace NSubstitute.Specs
|
|||
{
|
||||
public abstract class Concern : ConcernFor<GetCallSpec>
|
||||
{
|
||||
protected ICallStack _callStack;
|
||||
protected ICallCollection _callCollection;
|
||||
protected IPendingSpecification _pendingSpecification;
|
||||
protected ICallActions _callActions;
|
||||
protected ICallSpecificationFactory _callSpecificationFactory;
|
||||
|
||||
public override void Context()
|
||||
{
|
||||
_callStack = mock<ICallStack>();
|
||||
_callCollection = mock<ICallCollection>();
|
||||
_pendingSpecification = mock<IPendingSpecification>();
|
||||
_callActions = mock<ICallActions>();
|
||||
_callSpecificationFactory = mock<ICallSpecificationFactory>();
|
||||
|
@ -23,11 +25,11 @@ namespace NSubstitute.Specs
|
|||
|
||||
public override GetCallSpec CreateSubjectUnderTest()
|
||||
{
|
||||
return new GetCallSpec(_callStack, _pendingSpecification, _callSpecificationFactory, _callActions);
|
||||
return new GetCallSpec(_callCollection, _pendingSpecification, _callSpecificationFactory, _callActions);
|
||||
}
|
||||
}
|
||||
|
||||
public class When_getting_for_last_call_with_pending_spec_and_matching_args : Concern
|
||||
public class When_getting_for_pending_call_spec_with_pending_spec_and_matching_args : Concern
|
||||
{
|
||||
ICallSpecification _lastCallSpecification;
|
||||
ICallSpecification _result;
|
||||
|
@ -45,26 +47,28 @@ namespace NSubstitute.Specs
|
|||
}
|
||||
|
||||
[Test]
|
||||
public void Should_not_touch_call_stack()
|
||||
public void Should_not_touch_call_collection()
|
||||
{
|
||||
_callStack.did_not_receive(x => x.Pop());
|
||||
_callCollection.did_not_receive(x => x.Delete(It.IsAny<ICall>()));
|
||||
}
|
||||
|
||||
public override void Because()
|
||||
{
|
||||
_result = sut.FromLastCall(MatchArgs.AsSpecifiedInCall);
|
||||
_result = sut.FromPendingSpecification(MatchArgs.AsSpecifiedInCall);
|
||||
}
|
||||
|
||||
public override void Context()
|
||||
{
|
||||
base.Context();
|
||||
_lastCallSpecification = mock<ICallSpecification>();
|
||||
_pendingSpecification.stub(x => x.HasPendingCallSpec()).Return(true);
|
||||
_pendingSpecification.stub(x => x.UseCallSpec()).Return(_lastCallSpecification);
|
||||
_pendingSpecification.stub(x => x.HasPendingCallSpecInfo()).Return(true);
|
||||
|
||||
var specificationInfo = PendingSpecificationInfo.FromCallSpecification(_lastCallSpecification);
|
||||
_pendingSpecification.stub(x => x.UseCallSpecInfo()).Return(specificationInfo);
|
||||
}
|
||||
}
|
||||
|
||||
public class When_getting_for_last_call_with_pending_spec_and_setting_for_any_args : Concern
|
||||
public class When_getting_for_pending_spec_with_pending_spec_and_setting_for_any_args : Concern
|
||||
{
|
||||
ICallSpecification _callSpecUpdatedForAnyArgs;
|
||||
ICallSpecification _lastCallSpecification;
|
||||
|
@ -85,12 +89,12 @@ namespace NSubstitute.Specs
|
|||
[Test]
|
||||
public void Should_not_touch_call_stack()
|
||||
{
|
||||
_callStack.did_not_receive(x => x.Pop());
|
||||
_callCollection.did_not_receive(x => x.Delete(It.IsAny<ICall>()));
|
||||
}
|
||||
|
||||
public override void Because()
|
||||
{
|
||||
_result = sut.FromLastCall(MatchArgs.Any);
|
||||
_result = sut.FromPendingSpecification(MatchArgs.Any);
|
||||
}
|
||||
|
||||
public override void Context()
|
||||
|
@ -98,13 +102,16 @@ namespace NSubstitute.Specs
|
|||
base.Context();
|
||||
_callSpecUpdatedForAnyArgs = mock<ICallSpecification>();
|
||||
_lastCallSpecification = mock<ICallSpecification>();
|
||||
_pendingSpecification.stub(x => x.HasPendingCallSpec()).Return(true);
|
||||
_pendingSpecification.stub(x => x.UseCallSpec()).Return(_lastCallSpecification);
|
||||
|
||||
var specificationInfo = PendingSpecificationInfo.FromCallSpecification(_lastCallSpecification);
|
||||
|
||||
_pendingSpecification.stub(x => x.HasPendingCallSpecInfo()).Return(true);
|
||||
_pendingSpecification.stub(x => x.UseCallSpecInfo()).Return(specificationInfo);
|
||||
_lastCallSpecification.stub(x => x.CreateCopyThatMatchesAnyArguments()).Return(_callSpecUpdatedForAnyArgs);
|
||||
}
|
||||
}
|
||||
|
||||
public class When_getting_for_last_call_with_no_pending_call_spec : Concern
|
||||
public class When_getting_for_pending_spec_with_last_call_info : Concern
|
||||
{
|
||||
readonly MatchArgs _argMatchStrategy = MatchArgs.AsSpecifiedInCall;
|
||||
ICall _call;
|
||||
|
@ -112,27 +119,52 @@ namespace NSubstitute.Specs
|
|||
ICallSpecification _result;
|
||||
|
||||
[Test]
|
||||
public void Should_remove_the_call_from_those_recorded_and_add_it_to_configured_results()
|
||||
public void Should_take_last_call_from_pending_specification_info()
|
||||
{
|
||||
Assert.That(_result, Is.EqualTo(_callSpecification));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Should_delete_last_call_from_call_collection()
|
||||
{
|
||||
_callCollection.received(c => c.Delete(_call));
|
||||
}
|
||||
|
||||
public override void Because()
|
||||
{
|
||||
_result = sut.FromLastCall(_argMatchStrategy);
|
||||
_result = sut.FromPendingSpecification(_argMatchStrategy);
|
||||
}
|
||||
|
||||
public override void Context()
|
||||
{
|
||||
base.Context();
|
||||
|
||||
_call = mock<ICall>();
|
||||
_callStack.stub(x => x.Pop()).Return(_call);
|
||||
_pendingSpecification.stub(x => x.HasPendingCallSpec()).Return(false);
|
||||
|
||||
var specificationInfo = PendingSpecificationInfo.FromLastCall(_call);
|
||||
|
||||
_pendingSpecification.stub(x => x.HasPendingCallSpecInfo()).Return(true);
|
||||
_pendingSpecification.stub(x => x.UseCallSpecInfo()).Return(specificationInfo);
|
||||
|
||||
_callSpecification = mock<ICallSpecification>();
|
||||
_callSpecificationFactory.stub(x => x.CreateFrom(_call, _argMatchStrategy)).Return(_callSpecification);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public class When_no_pending_specification : Concern
|
||||
{
|
||||
[Test]
|
||||
public void Should_fail_with_exception()
|
||||
{
|
||||
Assert.That(() => sut.FromPendingSpecification(MatchArgs.AsSpecifiedInCall), Throws.InvalidOperationException);
|
||||
}
|
||||
|
||||
public override void Context()
|
||||
{
|
||||
base.Context();
|
||||
|
||||
_pendingSpecification.stub(s => s.HasPendingCallSpecInfo()).Return(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -7,6 +7,8 @@ namespace NSubstitute.Specs.Infrastructure
|
|||
{
|
||||
public class TestCallRouter : ICallRouter
|
||||
{
|
||||
public bool IsLastCallInfoPresent() => true;
|
||||
|
||||
public ConfiguredCall LastCallShouldReturn(IReturn returnValue, MatchArgs matchArgs)
|
||||
{
|
||||
return new ConfiguredCall(x => { });
|
||||
|
|
|
@ -168,7 +168,6 @@
|
|||
<Compile Include="PropertyHelperSpecs.cs" />
|
||||
<Compile Include="ReceivedExtensionSpec.cs" />
|
||||
<Compile Include="Routing\Handlers\RecordCallHandlerSpecs.cs" />
|
||||
<Compile Include="Routing\Handlers\RecordCallSpecificationHandlerSpecs.cs" />
|
||||
<Compile Include="Routing\Handlers\ReturnAutoValueSpecs.cs" />
|
||||
<Compile Include="Routing\Handlers\ReturnResultForTypeHandlerSpecs.cs" />
|
||||
<Compile Include="Routing\Handlers\ReturnConfiguredResultHandlerSpecs.cs" />
|
||||
|
@ -183,7 +182,7 @@
|
|||
<Compile Include="SubstitutionContextSpecs.cs" />
|
||||
<Compile Include="SubstituteFactorySpecs.cs" />
|
||||
<Compile Include="Infrastructure\BaseConcern.cs" />
|
||||
<Compile Include="CallStackSpecs.cs" />
|
||||
<Compile Include="CallCollectionSpecs.cs" />
|
||||
<Compile Include="Infrastructure\ConcernFor.cs" />
|
||||
<Compile Include="Infrastructure\ITemporaryChange.cs" />
|
||||
<Compile Include="Infrastructure\MockingAdaptor.cs" />
|
||||
|
|
|
@ -8,7 +8,14 @@ namespace NSubstitute.Specs
|
|||
{
|
||||
public abstract class Concern : ConcernFor<PendingSpecification>
|
||||
{
|
||||
public override PendingSpecification CreateSubjectUnderTest() { return new PendingSpecification(); }
|
||||
protected ISubstitutionContext _substitutionContext;
|
||||
|
||||
public override void Context()
|
||||
{
|
||||
_substitutionContext = mock<ISubstitutionContext>();
|
||||
}
|
||||
|
||||
public override PendingSpecification CreateSubjectUnderTest() { return new PendingSpecification(_substitutionContext); }
|
||||
}
|
||||
|
||||
public class When_no_pending_specification : Concern
|
||||
|
@ -16,13 +23,19 @@ namespace NSubstitute.Specs
|
|||
[Test]
|
||||
public void Should_not_have_a_spec()
|
||||
{
|
||||
Assert.That(sut.HasPendingCallSpec(), Is.False);
|
||||
Assert.That(sut.HasPendingCallSpecInfo(), Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Should_return_null_when_trying_to_use_spec()
|
||||
{
|
||||
Assert.That(sut.UseCallSpec(), Is.Null);
|
||||
Assert.That(sut.UseCallSpecInfo(), Is.Null);
|
||||
}
|
||||
|
||||
public override void Because()
|
||||
{
|
||||
base.Because();
|
||||
_substitutionContext.PendingSpecificationInfo = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -33,38 +46,73 @@ namespace NSubstitute.Specs
|
|||
[Test]
|
||||
public void Should_have_a_spec()
|
||||
{
|
||||
Assert.That(sut.HasPendingCallSpec(), Is.True);
|
||||
Assert.That(sut.HasPendingCallSpecInfo(), Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Should_return_the_spec_when_using_it()
|
||||
{
|
||||
Assert.That(sut.UseCallSpec(), Is.SameAs(_callSpec));
|
||||
var specInfo = sut.UseCallSpecInfo();
|
||||
Assert.That(specInfo.CallSpecification, Is.SameAs(_callSpec));
|
||||
Assert.That(specInfo.LastCall, Is.Null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Should_not_have_spec_after_it_is_used()
|
||||
{
|
||||
sut.UseCallSpec();
|
||||
Assert.That(sut.HasPendingCallSpec(), Is.False);
|
||||
sut.UseCallSpecInfo();
|
||||
Assert.That(sut.HasPendingCallSpecInfo(), Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Should_not_have_a_spec_after_it_is_cleared()
|
||||
{
|
||||
sut.Clear();
|
||||
Assert.That(sut.HasPendingCallSpec(), Is.False);
|
||||
Assert.That(sut.HasPendingCallSpecInfo(), Is.False);
|
||||
}
|
||||
|
||||
public override void Because()
|
||||
{
|
||||
sut.Set(_callSpec);
|
||||
base.Because();
|
||||
sut.SetCallSpecification(_callSpec);
|
||||
}
|
||||
|
||||
public override void Context()
|
||||
{
|
||||
base.Context();
|
||||
_callSpec = mock<ICallSpecification>();
|
||||
}
|
||||
}
|
||||
|
||||
public class When_a_last_call_info_has_been_set : Concern
|
||||
{
|
||||
ICall _call;
|
||||
|
||||
[Test]
|
||||
public void Should_have_a_spec()
|
||||
{
|
||||
Assert.That(sut.HasPendingCallSpecInfo(), Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Should_return_the_call_when_using_it()
|
||||
{
|
||||
var specInfo = sut.UseCallSpecInfo();
|
||||
Assert.That(specInfo.LastCall, Is.SameAs(_call));
|
||||
Assert.That(specInfo.CallSpecification, Is.Null);
|
||||
}
|
||||
|
||||
public override void Because()
|
||||
{
|
||||
base.Because();
|
||||
sut.SetLastCall(_call);
|
||||
}
|
||||
|
||||
public override void Context()
|
||||
{
|
||||
base.Context();
|
||||
_call = mock<ICall>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -10,14 +10,14 @@ namespace NSubstitute.Specs.Routing.Handlers
|
|||
public class When_handling_call_to_a_member : ConcernFor<RecordCallHandler>
|
||||
{
|
||||
ICall _call;
|
||||
ICallStack _callStack;
|
||||
ICallCollection _callCollection;
|
||||
SequenceNumberGenerator _sequenceNumberGenerator;
|
||||
RouteAction _result;
|
||||
|
||||
[Test]
|
||||
public void Should_record_call()
|
||||
{
|
||||
_callStack.received(x => x.Push(_call));
|
||||
_callCollection.received(x => x.Add(_call));
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -39,7 +39,7 @@ namespace NSubstitute.Specs.Routing.Handlers
|
|||
|
||||
public override void Context()
|
||||
{
|
||||
_callStack = mock<ICallStack>();
|
||||
_callCollection = mock<ICallCollection>();
|
||||
_call = mock<ICall>();
|
||||
_sequenceNumberGenerator = mock<SequenceNumberGenerator>();
|
||||
_sequenceNumberGenerator.stub(x => x.Next()).Return(42);
|
||||
|
@ -47,8 +47,8 @@ namespace NSubstitute.Specs.Routing.Handlers
|
|||
|
||||
public override RecordCallHandler CreateSubjectUnderTest()
|
||||
{
|
||||
return new RecordCallHandler(_callStack, _sequenceNumberGenerator);
|
||||
}
|
||||
return new RecordCallHandler(_callCollection, _sequenceNumberGenerator);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,58 +0,0 @@
|
|||
using NSubstitute.Core;
|
||||
using NSubstitute.Specs.Infrastructure;
|
||||
using NSubstitute.Routing.Handlers;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace NSubstitute.Specs.Routing.Handlers
|
||||
{
|
||||
public class RecordCallSpecificationHandlerSpecs
|
||||
{
|
||||
public class When_handling_call : ConcernFor<RecordCallSpecificationHandler>
|
||||
{
|
||||
IPendingSpecification _pendingSpec;
|
||||
ICallActions _callActions;
|
||||
ICallSpecificationFactory _callSpecFactory;
|
||||
ICall _call;
|
||||
ICallSpecification _callSpec;
|
||||
RouteAction _result;
|
||||
|
||||
[Test]
|
||||
public void Should_set_pending_call_spec()
|
||||
{
|
||||
_pendingSpec.received(x => x.Set(_callSpec));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Should_add_any_specified_actions()
|
||||
{
|
||||
_callActions.received(x => x.Add(_callSpec));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Should_continue_route()
|
||||
{
|
||||
Assert.That(_result, Is.EqualTo(RouteAction.Continue()));
|
||||
}
|
||||
|
||||
public override void Because()
|
||||
{
|
||||
_result = sut.Handle(_call);
|
||||
}
|
||||
|
||||
public override void Context()
|
||||
{
|
||||
_call = mock<ICall>();
|
||||
_callSpec = mock<ICallSpecification>();
|
||||
_callActions = mock<ICallActions>();
|
||||
_pendingSpec = mock<IPendingSpecification>();
|
||||
_callSpecFactory = mock<ICallSpecificationFactory>();
|
||||
_callSpecFactory.stub(x => x.CreateFrom(_call, MatchArgs.AsSpecifiedInCall)).Return(_callSpec);
|
||||
}
|
||||
|
||||
public override RecordCallSpecificationHandler CreateSubjectUnderTest()
|
||||
{
|
||||
return new RecordCallSpecificationHandler(_pendingSpec, _callSpecFactory, _callActions);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
using System.Threading;
|
||||
using NSubstitute.Core;
|
||||
using NSubstitute.Core.Arguments;
|
||||
using NSubstitute.Exceptions;
|
||||
|
@ -51,6 +52,8 @@ namespace NSubstitute.Specs
|
|||
{
|
||||
base.Context();
|
||||
_callRouter = mock<ICallRouter>();
|
||||
_callRouter.stub(c => c.IsLastCallInfoPresent()).Return(true);
|
||||
|
||||
_valueToReturn = mock<IReturn>();
|
||||
}
|
||||
}
|
||||
|
@ -64,6 +67,32 @@ namespace NSubstitute.Specs
|
|||
}
|
||||
}
|
||||
|
||||
public class When_trying_to_set_a_return_value_when_router_cannot_configure_last_call : Concern
|
||||
{
|
||||
private ICallRouter _callRouter;
|
||||
|
||||
[Test]
|
||||
public void Should_throw_that_last_call_cannot_be_configured()
|
||||
{
|
||||
Assert.Throws<CouldNotSetReturnDueToMissingInfoAboutLastCallException>(() => sut.LastCallShouldReturn(mock<IReturn>(), MatchArgs.AsSpecifiedInCall));
|
||||
}
|
||||
|
||||
public override void Because()
|
||||
{
|
||||
base.Because();
|
||||
|
||||
_callRouter.stub(c => c.IsLastCallInfoPresent()).Return(false);
|
||||
sut.LastCallRouter(_callRouter);
|
||||
}
|
||||
|
||||
public override void Context()
|
||||
{
|
||||
base.Context();
|
||||
|
||||
_callRouter = mock<ICallRouter>();
|
||||
}
|
||||
}
|
||||
|
||||
public class When_getting_call_router_for_a_substitute : Concern
|
||||
{
|
||||
private ICallRouter _routerForSubstitute;
|
||||
|
@ -132,6 +161,39 @@ namespace NSubstitute.Specs
|
|||
}
|
||||
}
|
||||
|
||||
public class When_working_with_thread_static_pending_spec_info : Concern
|
||||
{
|
||||
PendingSpecificationInfo _specInfo;
|
||||
|
||||
[Test]
|
||||
public void Storage_is_bound_to_thread()
|
||||
{
|
||||
var thread = new Thread(() => { sut.PendingSpecificationInfo = _specInfo; })
|
||||
{
|
||||
IsBackground = true
|
||||
};
|
||||
|
||||
thread.Start();
|
||||
thread.Join();
|
||||
|
||||
Assert.That(sut.PendingSpecificationInfo, Is.Null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Set_value_is_read()
|
||||
{
|
||||
sut.PendingSpecificationInfo = _specInfo;
|
||||
Assert.That(sut.PendingSpecificationInfo, Is.EqualTo(_specInfo));
|
||||
}
|
||||
|
||||
public override void Context()
|
||||
{
|
||||
base.Context();
|
||||
|
||||
_specInfo = PendingSpecificationInfo.FromLastCall(mock<ICall>());
|
||||
}
|
||||
}
|
||||
|
||||
public class When_accessing_current_instance : StaticConcern
|
||||
{
|
||||
[Test]
|
||||
|
|
|
@ -47,7 +47,12 @@ namespace NSubstitute.Core.Arguments
|
|||
|
||||
public IArgumentSpecification CreateCopyMatchingAnyArgOfType(Type requiredType)
|
||||
{
|
||||
return new ArgumentSpecification(requiredType, new AnyArgumentMatcher(requiredType), RunActionIfTypeIsCompatible);
|
||||
//Don't pass RunActionIfTypeIsCompatible method if no action is present.
|
||||
//Otherwise, unnecessary closure will keep reference to this and will keep it alive.
|
||||
return new ArgumentSpecification(
|
||||
requiredType,
|
||||
new AnyArgumentMatcher(requiredType),
|
||||
_action == NoOpAction ? NoOpAction : RunActionIfTypeIsCompatible);
|
||||
}
|
||||
|
||||
public void RunAction(object argument)
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace NSubstitute.Core
|
||||
{
|
||||
public class CallCollection : ICallCollection, IReceivedCalls
|
||||
{
|
||||
ConcurrentQueue<CallWrapper> _callWrappers = new ConcurrentQueue<CallWrapper>();
|
||||
|
||||
public void Add(ICall call)
|
||||
{
|
||||
if (call == null) throw new ArgumentNullException(nameof(call));
|
||||
_callWrappers.Enqueue(new CallWrapper(call));
|
||||
}
|
||||
|
||||
public void Delete(ICall call)
|
||||
{
|
||||
if (call == null) throw new ArgumentNullException(nameof(call));
|
||||
|
||||
var callWrapper = _callWrappers.FirstOrDefault(w => !w.IsDeleted && call.Equals(w.Call));
|
||||
if (callWrapper == null) throw new InvalidOperationException("Collection doesn't contain the call.");
|
||||
|
||||
callWrapper.Delete();
|
||||
}
|
||||
|
||||
public IEnumerable<ICall> AllCalls()
|
||||
{
|
||||
return _callWrappers.ToArray()
|
||||
.Where(w => !w.IsDeleted)
|
||||
.Select(w => w.Call);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
//Queue doesn't have a Clear method.
|
||||
_callWrappers = new ConcurrentQueue<CallWrapper>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wrapper to track that particular entry was deleted.
|
||||
/// That is needed because concurrent collections don't have a Delete method.
|
||||
/// We null the hold value to not leak memory.
|
||||
/// </summary>
|
||||
private class CallWrapper
|
||||
{
|
||||
public ICall Call { get; private set; }
|
||||
public bool IsDeleted => Call == null;
|
||||
|
||||
public CallWrapper(ICall call)
|
||||
{
|
||||
Call = call;
|
||||
}
|
||||
|
||||
public void Delete()
|
||||
{
|
||||
Call = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -64,6 +64,11 @@ namespace NSubstitute.Core
|
|||
return routeToUseForThisCall.Handle(call);
|
||||
}
|
||||
|
||||
public bool IsLastCallInfoPresent()
|
||||
{
|
||||
return _substituteState.PendingSpecification.HasPendingCallSpecInfo();
|
||||
}
|
||||
|
||||
public ConfiguredCall LastCallShouldReturn(IReturn returnValue, MatchArgs matchArgs)
|
||||
{
|
||||
return _substituteState.ConfigureCall.SetResultForLastCall(returnValue, matchArgs);
|
||||
|
|
|
@ -1,34 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace NSubstitute.Core
|
||||
{
|
||||
public class CallStack : ICallStack, IReceivedCalls
|
||||
{
|
||||
readonly System.Collections.Concurrent.ConcurrentStack<ICall> _stack
|
||||
= new System.Collections.Concurrent.ConcurrentStack<ICall>();
|
||||
|
||||
public void Push(ICall call)
|
||||
{
|
||||
_stack.Push(call);
|
||||
}
|
||||
|
||||
public ICall Pop()
|
||||
{
|
||||
ICall call;
|
||||
if (!_stack.TryPop(out call)) throw new InvalidOperationException("Stack empty");
|
||||
return call;
|
||||
}
|
||||
|
||||
public IEnumerable<ICall> AllCalls()
|
||||
{
|
||||
return _stack.ToArray().Reverse();
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
_stack.Clear();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -17,7 +17,7 @@ namespace NSubstitute.Core
|
|||
|
||||
public ConfiguredCall SetResultForLastCall(IReturn valueToReturn, MatchArgs matchArgs)
|
||||
{
|
||||
var spec = _getCallSpec.FromLastCall(matchArgs);
|
||||
var spec = _getCallSpec.FromPendingSpecification(matchArgs);
|
||||
CheckResultIsCompatibleWithCall(valueToReturn, spec);
|
||||
_configuredResults.SetResult(spec, valueToReturn);
|
||||
return new ConfiguredCall(action => _callActions.Add(spec, action));
|
||||
|
|
|
@ -1,26 +1,40 @@
|
|||
namespace NSubstitute.Core
|
||||
using System;
|
||||
|
||||
namespace NSubstitute.Core
|
||||
{
|
||||
public class GetCallSpec : IGetCallSpec
|
||||
{
|
||||
private readonly ICallStack _callStack;
|
||||
private readonly ICallCollection _callCollection;
|
||||
private readonly IPendingSpecification _pendingSpecification;
|
||||
private readonly ICallSpecificationFactory _callSpecificationFactory;
|
||||
private readonly ICallActions _callActions;
|
||||
|
||||
public GetCallSpec(ICallStack callStack, IPendingSpecification pendingSpecification,
|
||||
public GetCallSpec(ICallCollection callCollection, IPendingSpecification pendingSpecification,
|
||||
ICallSpecificationFactory callSpecificationFactory, ICallActions callActions)
|
||||
{
|
||||
_callStack = callStack;
|
||||
_callCollection = callCollection;
|
||||
_pendingSpecification = pendingSpecification;
|
||||
_callSpecificationFactory = callSpecificationFactory;
|
||||
_callActions = callActions;
|
||||
}
|
||||
|
||||
public ICallSpecification FromLastCall(MatchArgs matchArgs)
|
||||
public ICallSpecification FromPendingSpecification(MatchArgs matchArgs)
|
||||
{
|
||||
return _pendingSpecification.HasPendingCallSpec()
|
||||
? FromExistingSpec(_pendingSpecification.UseCallSpec(), matchArgs)
|
||||
: FromCall(_callStack.Pop(), matchArgs);
|
||||
if (!_pendingSpecification.HasPendingCallSpecInfo())
|
||||
{
|
||||
throw new InvalidOperationException("No pending specification or previous call info.");
|
||||
}
|
||||
|
||||
var pendingSpecInfo = _pendingSpecification.UseCallSpecInfo();
|
||||
if (pendingSpecInfo.CallSpecification != null)
|
||||
{
|
||||
return FromExistingSpec(pendingSpecInfo.CallSpecification, matchArgs);
|
||||
}
|
||||
|
||||
var lastCall = pendingSpecInfo.LastCall;
|
||||
_callCollection.Delete(lastCall);
|
||||
|
||||
return FromCall(lastCall, matchArgs);
|
||||
}
|
||||
|
||||
public ICallSpecification FromCall(ICall call, MatchArgs matchArgs)
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
namespace NSubstitute.Core
|
||||
{
|
||||
public interface ICallCollection
|
||||
{
|
||||
void Add(ICall call);
|
||||
void Delete(ICall call);
|
||||
}
|
||||
}
|
|
@ -6,6 +6,7 @@ namespace NSubstitute.Core
|
|||
{
|
||||
public interface ICallRouter
|
||||
{
|
||||
bool IsLastCallInfoPresent();
|
||||
ConfiguredCall LastCallShouldReturn(IReturn returnValue, MatchArgs matchArgs);
|
||||
object Route(ICall call);
|
||||
IEnumerable<ICall> ReceivedCalls();
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
namespace NSubstitute.Core
|
||||
{
|
||||
public interface ICallStack
|
||||
{
|
||||
void Push(ICall call);
|
||||
ICall Pop();
|
||||
}
|
||||
}
|
|
@ -2,7 +2,7 @@
|
|||
{
|
||||
public interface IGetCallSpec
|
||||
{
|
||||
ICallSpecification FromLastCall(MatchArgs matchArgs);
|
||||
ICallSpecification FromPendingSpecification(MatchArgs matchArgs);
|
||||
ICallSpecification FromExistingSpec(ICallSpecification spec, MatchArgs matchArgs);
|
||||
ICallSpecification FromCall(ICall call, MatchArgs matchArgs);
|
||||
}
|
||||
|
|
|
@ -2,9 +2,10 @@
|
|||
{
|
||||
public interface IPendingSpecification
|
||||
{
|
||||
bool HasPendingCallSpec();
|
||||
ICallSpecification UseCallSpec();
|
||||
void Set(ICallSpecification callSpecification);
|
||||
bool HasPendingCallSpecInfo();
|
||||
PendingSpecificationInfo UseCallSpecInfo();
|
||||
void SetCallSpecification(ICallSpecification callSpecification);
|
||||
void SetLastCall(ICall lastCall);
|
||||
void Clear();
|
||||
}
|
||||
}
|
|
@ -5,7 +5,7 @@ namespace NSubstitute.Core
|
|||
public interface ISubstituteState
|
||||
{
|
||||
ISubstitutionContext SubstitutionContext { get; }
|
||||
ICallStack CallStack { get; }
|
||||
ICallCollection CallCollection { get; }
|
||||
IReceivedCalls ReceivedCalls { get; }
|
||||
IPendingSpecification PendingSpecification { get; }
|
||||
ICallResults CallResults { get; }
|
||||
|
@ -20,6 +20,5 @@ namespace NSubstitute.Core
|
|||
ICallBaseExclusions CallBaseExclusions { get; }
|
||||
IResultsForType ResultsForType { get; }
|
||||
ICustomHandlers CustomHandlers { get; }
|
||||
void ClearUnusedCallSpecs();
|
||||
}
|
||||
}
|
|
@ -9,6 +9,7 @@ namespace NSubstitute.Core
|
|||
{
|
||||
ISubstituteFactory SubstituteFactory { get; }
|
||||
SequenceNumberGenerator SequenceNumberGenerator { get; }
|
||||
PendingSpecificationInfo PendingSpecificationInfo { get; set; }
|
||||
ConfiguredCall LastCallShouldReturn(IReturn value, MatchArgs matchArgs);
|
||||
void LastCallRouter(ICallRouter callRouter);
|
||||
ICallRouter GetCallRouterFor(object substitute);
|
||||
|
|
|
@ -1,29 +1,41 @@
|
|||
using System;
|
||||
|
||||
namespace NSubstitute.Core
|
||||
{
|
||||
public class PendingSpecification : IPendingSpecification
|
||||
{
|
||||
ICallSpecification _pendingSpec;
|
||||
private readonly ISubstitutionContext _substitutionContext;
|
||||
|
||||
public bool HasPendingCallSpec()
|
||||
public PendingSpecification(ISubstitutionContext substitutionContext)
|
||||
{
|
||||
return _pendingSpec != null;
|
||||
_substitutionContext = substitutionContext;
|
||||
}
|
||||
|
||||
public ICallSpecification UseCallSpec()
|
||||
public bool HasPendingCallSpecInfo()
|
||||
{
|
||||
var specToUse = _pendingSpec;
|
||||
_pendingSpec = null;
|
||||
return specToUse;
|
||||
return _substitutionContext.PendingSpecificationInfo != null;
|
||||
}
|
||||
|
||||
public PendingSpecificationInfo UseCallSpecInfo()
|
||||
{
|
||||
var info = _substitutionContext.PendingSpecificationInfo;
|
||||
Clear();
|
||||
return info;
|
||||
}
|
||||
|
||||
public void SetCallSpecification(ICallSpecification callSpecification)
|
||||
{
|
||||
_substitutionContext.PendingSpecificationInfo = PendingSpecificationInfo.FromCallSpecification(callSpecification);
|
||||
}
|
||||
|
||||
public void SetLastCall(ICall lastCall)
|
||||
{
|
||||
_substitutionContext.PendingSpecificationInfo = PendingSpecificationInfo.FromLastCall(lastCall);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
UseCallSpec();
|
||||
}
|
||||
|
||||
public void Set(ICallSpecification callSpecification)
|
||||
{
|
||||
_pendingSpec = callSpecification;
|
||||
_substitutionContext.PendingSpecificationInfo = null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
namespace NSubstitute.Core
|
||||
{
|
||||
public class PendingSpecificationInfo
|
||||
{
|
||||
public ICallSpecification CallSpecification { get; }
|
||||
public ICall LastCall { get; }
|
||||
|
||||
private PendingSpecificationInfo(ICallSpecification callSpecification, ICall lastCall)
|
||||
{
|
||||
CallSpecification = callSpecification;
|
||||
LastCall = lastCall;
|
||||
}
|
||||
|
||||
public static PendingSpecificationInfo FromLastCall(ICall lastCall)
|
||||
{
|
||||
return new PendingSpecificationInfo(null, lastCall);
|
||||
}
|
||||
|
||||
public static PendingSpecificationInfo FromCallSpecification(ICallSpecification callSpecification)
|
||||
{
|
||||
return new PendingSpecificationInfo(callSpecification, null);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5,9 +5,9 @@ namespace NSubstitute.Core
|
|||
public class SubstituteState : ISubstituteState
|
||||
{
|
||||
public ISubstitutionContext SubstitutionContext { get; private set; }
|
||||
public ICallStack CallStack { get; private set; }
|
||||
public ICallCollection CallCollection { get; }
|
||||
public IReceivedCalls ReceivedCalls { get; private set; }
|
||||
public IPendingSpecification PendingSpecification { get; private set; }
|
||||
public IPendingSpecification PendingSpecification { get; }
|
||||
public ICallResults CallResults { get; private set; }
|
||||
public ICallSpecificationFactory CallSpecificationFactory { get; private set; }
|
||||
public ICallActions CallActions { get; private set; }
|
||||
|
@ -28,10 +28,12 @@ namespace NSubstitute.Core
|
|||
SequenceNumberGenerator = substitutionContext.SequenceNumberGenerator;
|
||||
var substituteFactory = substitutionContext.SubstituteFactory;
|
||||
var callInfoFactory = new CallInfoFactory();
|
||||
var callStack = new CallStack();
|
||||
CallStack = callStack;
|
||||
ReceivedCalls = callStack;
|
||||
PendingSpecification = new PendingSpecification();
|
||||
|
||||
var callCollection = new CallCollection();
|
||||
CallCollection = callCollection;
|
||||
ReceivedCalls = callCollection;
|
||||
|
||||
PendingSpecification = new PendingSpecification(substitutionContext);
|
||||
CallResults = new CallResults(callInfoFactory);
|
||||
AutoValuesCallResults = new CallResults(callInfoFactory);
|
||||
CallSpecificationFactory = CallSpecificationFactoryFactoryYesThatsRight.CreateCallSpecFactory();
|
||||
|
@ -39,26 +41,22 @@ namespace NSubstitute.Core
|
|||
CallBaseExclusions = new CallBaseExclusions();
|
||||
ResultsForType = new ResultsForType(callInfoFactory);
|
||||
CustomHandlers = new CustomHandlers(this);
|
||||
var getCallSpec = new GetCallSpec(callStack, PendingSpecification, CallSpecificationFactory, CallActions);
|
||||
var getCallSpec = new GetCallSpec(callCollection, PendingSpecification, CallSpecificationFactory, CallActions);
|
||||
ConfigureCall = new ConfigureCall(CallResults, CallActions, getCallSpec);
|
||||
EventHandlerRegistry = new EventHandlerRegistry();
|
||||
|
||||
AutoValueProviders = new IAutoValueProvider[] {
|
||||
#if NET45 || NETSTANDARD1_5
|
||||
new AutoObservableProvider(() => AutoValueProviders),
|
||||
new AutoQueryableProvider(),
|
||||
#endif
|
||||
new AutoSubstituteProvider(substituteFactory),
|
||||
new AutoStringProvider(),
|
||||
new AutoSubstituteProvider(substituteFactory),
|
||||
new AutoStringProvider(),
|
||||
new AutoArrayProvider(),
|
||||
#if (NET4 || NET45 || NETSTANDARD1_5)
|
||||
new AutoTaskProvider(() => AutoValueProviders),
|
||||
#endif
|
||||
};
|
||||
}
|
||||
|
||||
public void ClearUnusedCallSpecs()
|
||||
{
|
||||
PendingSpecification.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ namespace NSubstitute.Core
|
|||
readonly RobustThreadLocal<IList<IArgumentSpecification>> _argumentSpecifications = new RobustThreadLocal<IList<IArgumentSpecification>>(() => new List<IArgumentSpecification>());
|
||||
readonly RobustThreadLocal<Func<ICall, object[]>> _getArgumentsForRaisingEvent = new RobustThreadLocal<Func<ICall, object[]>>();
|
||||
readonly RobustThreadLocal<Query> _currentQuery = new RobustThreadLocal<Query>();
|
||||
readonly RobustThreadLocal<PendingSpecificationInfo> _pendingSpecificationInfo = new RobustThreadLocal<PendingSpecificationInfo>();
|
||||
|
||||
static SubstitutionContext()
|
||||
{
|
||||
|
@ -45,16 +46,25 @@ namespace NSubstitute.Core
|
|||
public SequenceNumberGenerator SequenceNumberGenerator { get { return _sequenceNumberGenerator; } }
|
||||
public bool IsQuerying { get { return _currentQuery.Value != null; } }
|
||||
|
||||
public PendingSpecificationInfo PendingSpecificationInfo
|
||||
{
|
||||
get { return _pendingSpecificationInfo.Value; }
|
||||
set { _pendingSpecificationInfo.Value = value; }
|
||||
}
|
||||
|
||||
public ConfiguredCall LastCallShouldReturn(IReturn value, MatchArgs matchArgs)
|
||||
{
|
||||
if (_lastCallRouter.Value == null) throw new CouldNotSetReturnDueToNoLastCallException();
|
||||
var lastCallRouter = _lastCallRouter.Value;
|
||||
if (lastCallRouter == null) throw new CouldNotSetReturnDueToNoLastCallException();
|
||||
if (!lastCallRouter.IsLastCallInfoPresent()) throw new CouldNotSetReturnDueToMissingInfoAboutLastCallException();
|
||||
if (_argumentSpecifications.Value.Any())
|
||||
{
|
||||
//Clear invalid arg specs so they will not affect other tests
|
||||
_argumentSpecifications.Value.Clear();
|
||||
throw new UnexpectedArgumentMatcherException();
|
||||
}
|
||||
var configuredCall = _lastCallRouter.Value.LastCallShouldReturn(value, matchArgs);
|
||||
|
||||
var configuredCall = lastCallRouter.LastCallShouldReturn(value, matchArgs);
|
||||
ClearLastCallRouter();
|
||||
return configuredCall;
|
||||
}
|
||||
|
|
|
@ -42,6 +42,22 @@ namespace NSubstitute.Exceptions
|
|||
#endif
|
||||
}
|
||||
|
||||
#if NET35 || NET4 || NET45
|
||||
[Serializable]
|
||||
#endif
|
||||
public class CouldNotSetReturnDueToMissingInfoAboutLastCallException : CouldNotSetReturnException
|
||||
{
|
||||
public CouldNotSetReturnDueToMissingInfoAboutLastCallException() : base("Could not find information about the last call to return from.")
|
||||
{
|
||||
}
|
||||
|
||||
#if NET35 || NET4 || NET45
|
||||
protected CouldNotSetReturnDueToMissingInfoAboutLastCallException(SerializationInfo info, StreamingContext context) : base(info, context)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if NET35 || NET4 || NET45
|
||||
[Serializable]
|
||||
#endif
|
||||
|
|
|
@ -4,16 +4,16 @@ namespace NSubstitute.Routing.Handlers
|
|||
{
|
||||
public class ClearUnusedCallSpecHandler : ICallHandler
|
||||
{
|
||||
private readonly ISubstituteState _state;
|
||||
private readonly IPendingSpecification _pendingSpecification;
|
||||
|
||||
public ClearUnusedCallSpecHandler(ISubstituteState state)
|
||||
public ClearUnusedCallSpecHandler(IPendingSpecification pendingSpecification)
|
||||
{
|
||||
_state = state;
|
||||
_pendingSpecification = pendingSpecification;
|
||||
}
|
||||
|
||||
public RouteAction Handle(ICall call)
|
||||
{
|
||||
_state.ClearUnusedCallSpecs();
|
||||
_pendingSpecification.Clear();
|
||||
return RouteAction.Continue();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,19 +4,19 @@ namespace NSubstitute.Routing.Handlers
|
|||
{
|
||||
public class RecordCallHandler : ICallHandler
|
||||
{
|
||||
private readonly ICallStack _callStack;
|
||||
private readonly ICallCollection _callCollection;
|
||||
private readonly SequenceNumberGenerator _generator;
|
||||
|
||||
public RecordCallHandler(ICallStack callStack, SequenceNumberGenerator generator)
|
||||
public RecordCallHandler(ICallCollection callCollection, SequenceNumberGenerator generator)
|
||||
{
|
||||
_callStack = callStack;
|
||||
_callCollection = callCollection;
|
||||
_generator = generator;
|
||||
}
|
||||
|
||||
public RouteAction Handle(ICall call)
|
||||
{
|
||||
call.AssignSequenceNumber(_generator.Next());
|
||||
_callStack.Push(call);
|
||||
_callCollection.Add(call);
|
||||
return RouteAction.Continue();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ namespace NSubstitute.Routing.Handlers
|
|||
public RouteAction Handle(ICall call)
|
||||
{
|
||||
var callSpec = _callSpecificationFactory.CreateFrom(call, MatchArgs.AsSpecifiedInCall);
|
||||
_pendingCallSpecification.Set(callSpec);
|
||||
_pendingCallSpecification.SetCallSpecification(callSpec);
|
||||
_callActions.Add(callSpec);
|
||||
return RouteAction.Continue();
|
||||
}
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
using NSubstitute.Core;
|
||||
|
||||
namespace NSubstitute.Routing.Handlers
|
||||
{
|
||||
public class TrackLastCallHandler : ICallHandler
|
||||
{
|
||||
private readonly IPendingSpecification _pendingSpecification;
|
||||
|
||||
public TrackLastCallHandler(IPendingSpecification pendingSpecification)
|
||||
{
|
||||
_pendingSpecification = pendingSpecification;
|
||||
}
|
||||
|
||||
public RouteAction Handle(ICall call)
|
||||
{
|
||||
_pendingSpecification.SetLastCall(call);
|
||||
return RouteAction.Continue();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -9,7 +9,7 @@ namespace NSubstitute.Routing
|
|||
public IRoute CallQuery(ISubstituteState state)
|
||||
{
|
||||
return new Route(new ICallHandler[] {
|
||||
new ClearUnusedCallSpecHandler(state)
|
||||
new ClearUnusedCallSpecHandler(state.PendingSpecification)
|
||||
, new AddCallToQueryResultHandler(state.SubstitutionContext, state.CallSpecificationFactory)
|
||||
, new ReturnConfiguredResultHandler(state.CallResults)
|
||||
, new ReturnAutoValue(AutoValueBehaviour.UseValueForSubsequentCalls, state.AutoValueProviders, state.AutoValuesCallResults, state.CallSpecificationFactory)
|
||||
|
@ -20,7 +20,7 @@ namespace NSubstitute.Routing
|
|||
{
|
||||
return new Route(new ICallHandler[] {
|
||||
new ClearLastCallRouterHandler(state.SubstitutionContext)
|
||||
, new ClearUnusedCallSpecHandler(state)
|
||||
, new ClearUnusedCallSpecHandler(state.PendingSpecification)
|
||||
, new CheckReceivedCallsHandler(state.ReceivedCalls, state.CallSpecificationFactory, new ReceivedCallsExceptionThrower(), matchArgs, requiredQuantity)
|
||||
, new ReturnAutoValue(AutoValueBehaviour.ReturnAndForgetValue, state.AutoValueProviders, state.AutoValuesCallResults, state.CallSpecificationFactory)
|
||||
, ReturnDefaultForReturnTypeHandler()
|
||||
|
@ -30,7 +30,7 @@ namespace NSubstitute.Routing
|
|||
{
|
||||
return new Route(new ICallHandler[] {
|
||||
new ClearLastCallRouterHandler(state.SubstitutionContext)
|
||||
, new ClearUnusedCallSpecHandler(state)
|
||||
, new ClearUnusedCallSpecHandler(state.PendingSpecification)
|
||||
, new SetActionForCallHandler(state.CallSpecificationFactory, state.CallActions, doAction, matchArgs)
|
||||
, ReturnDefaultForReturnTypeHandler()
|
||||
});
|
||||
|
@ -39,7 +39,7 @@ namespace NSubstitute.Routing
|
|||
{
|
||||
return new Route(new ICallHandler[] {
|
||||
new ClearLastCallRouterHandler(state.SubstitutionContext)
|
||||
, new ClearUnusedCallSpecHandler(state)
|
||||
, new ClearUnusedCallSpecHandler(state.PendingSpecification)
|
||||
, new DoNotCallBaseForCallHandler(state.CallSpecificationFactory, state.CallBaseExclusions, matchArgs)
|
||||
, ReturnDefaultForReturnTypeHandler()
|
||||
});
|
||||
|
@ -48,7 +48,7 @@ namespace NSubstitute.Routing
|
|||
{
|
||||
return new Route(new ICallHandler[] {
|
||||
new ClearLastCallRouterHandler(state.SubstitutionContext)
|
||||
, new ClearUnusedCallSpecHandler(state)
|
||||
, new ClearUnusedCallSpecHandler(state.PendingSpecification)
|
||||
, new RaiseEventHandler(state.EventHandlerRegistry, getEventArguments)
|
||||
, ReturnDefaultForReturnTypeHandler()
|
||||
});
|
||||
|
@ -67,8 +67,9 @@ namespace NSubstitute.Routing
|
|||
public IRoute RecordReplay(ISubstituteState state)
|
||||
{
|
||||
return new Route(RouteType.RecordReplay, new ICallHandler[] {
|
||||
new ClearUnusedCallSpecHandler(state)
|
||||
, new RecordCallHandler(state.CallStack, state.SequenceNumberGenerator)
|
||||
new ClearUnusedCallSpecHandler(state.PendingSpecification)
|
||||
, new TrackLastCallHandler(state.PendingSpecification)
|
||||
, new RecordCallHandler(state.CallCollection, state.SequenceNumberGenerator)
|
||||
, new EventSubscriptionHandler(state.EventHandlerRegistry)
|
||||
, new PropertySetterHandler(new PropertyHelper(), state.ConfigureCall)
|
||||
, new DoActionsCallHandler(state.CallActions)
|
||||
|
|
Загрузка…
Ссылка в новой задаче