diff --git a/Source/Capture.cs b/Source/Capture.cs new file mode 100644 index 000000000..ef5340359 --- /dev/null +++ b/Source/Capture.cs @@ -0,0 +1,97 @@ +//Copyright (c) 2007. Clarius Consulting, Manas Technology Solutions, InSTEDD +//http://code.google.com/p/moq/ +//All rights reserved. + +//Redistribution and use in source and binary forms, +//with or without modification, are permitted provided +//that the following conditions are met: + +// * Redistributions of source code must retain the +// above copyright notice, this list of conditions and +// the following disclaimer. + +// * Redistributions in binary form must reproduce +// the above copyright notice, this list of conditions +// and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// * Neither the name of Clarius Consulting, Manas Technology Solutions or InSTEDD nor the +// names of its contributors may be used to endorse +// or promote products derived from this software +// without specific prior written permission. + +//THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND +//CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +//INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +//MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +//DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +//CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +//SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +//BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +//SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +//INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +//WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +//NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +//OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +//SUCH DAMAGE. + +//[This is the BSD license, see +// http://www.opensource.org/licenses/bsd-license.php] + +using System; +using System.Collections.Generic; +using System.Linq.Expressions; + +namespace Moq +{ + /// + /// Allows to create parameter captures in setup expressions. + /// + public static class Capture + { + /// + /// Creates a parameter capture that will store items in a collection. + /// + /// The captured object item + /// The collection that will store captured parameter values + /// + /// Arrange code: + /// + /// var parameters = new List{string}(); + /// mock.Setup(x => x.DoSomething(Capture.In(parameters))); + /// + /// Assert code: + /// + /// Assert.Equal("Hello!", parameters.Single()); + /// + /// + public static T In(IList collection) + { + var capture = new MockCapture(collection); + return capture.CaptureAny(); + } + + /// + /// Creates a parameter capture that will store specific items in a collection. + /// + /// The captured object item + /// The collection that will store captured parameter values + /// A predicate used to filter captured parameters + /// + /// Arrange code: + /// + /// var parameters = new List{string}(); + /// mock.Setup(x => x.DoSomething(Capture.In(parameters, p => p.StartsWith("W")))); + /// + /// Assert code: + /// + /// Assert.Equal("Hello!", parameters.Single()); + /// + /// + public static T In(IList collection, Expression> match) + { + var capture = new MockCapture(collection); + return capture.Capture(match); + } + } +} \ No newline at end of file diff --git a/Source/MockCapture.cs b/Source/MockCapture.cs new file mode 100644 index 000000000..90049b4c3 --- /dev/null +++ b/Source/MockCapture.cs @@ -0,0 +1,148 @@ +//Copyright (c) 2007. Clarius Consulting, Manas Technology Solutions, InSTEDD +//http://code.google.com/p/moq/ +//All rights reserved. + +//Redistribution and use in source and binary forms, +//with or without modification, are permitted provided +//that the following conditions are met: + +// * Redistributions of source code must retain the +// above copyright notice, this list of conditions and +// the following disclaimer. + +// * Redistributions in binary form must reproduce +// the above copyright notice, this list of conditions +// and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// * Neither the name of Clarius Consulting, Manas Technology Solutions or InSTEDD nor the +// names of its contributors may be used to endorse +// or promote products derived from this software +// without specific prior written permission. + +//THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND +//CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +//INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +//MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +//DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +//CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +//SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +//BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +//SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +//INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +//WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +//NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +//OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +//SUCH DAMAGE. + +//[This is the BSD license, see +// http://www.opensource.org/licenses/bsd-license.php] + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Linq.Expressions; + +namespace Moq +{ + /// + /// Captures mock parameters. + /// + /// + /// Arrange code: + /// + /// var capture = new MockCapture{string}(); + /// mock.Setup(x => x.DoSomething(capture.CaptureAny())); + /// + /// Assert code: + /// + /// Assert.Equal("Hello!", capture.Last); + /// + /// + public class MockCapture + { + private static readonly Func identity = _ => _; + private readonly IList capturedValues = new List(); + private readonly Func captureCallback; + + /// + /// Initializes an instance of the capture. + /// + public MockCapture() + : this(identity) + { + } + + /// + /// Initializes an instance of the capture. + /// + /// An action to run on captured value + public MockCapture(Action captureCallback) + : this(CreateCaptureCallback(captureCallback)) + { + } + + /// + /// Initializes an instance of the capture. + /// + /// A transform function to apply to captured values + public MockCapture(Func captureCallback) + { + this.captureCallback = captureCallback; + this.capturedValues = new List(); + this.Values = new ReadOnlyCollection(this.capturedValues); + } + + internal MockCapture(IList capturedValues) + { + this.captureCallback = identity; + this.capturedValues = capturedValues; + this.Values = new ReadOnlyCollection(this.capturedValues); + } + + /// + /// Gets all the captured values. + /// + public IReadOnlyList Values { get; private set; } + + /// + /// Gets the last captured value. + /// + public T LastValue { get { return this.Values.Last(); } } + + /// + /// Gets the parameter to use in the setup expression. + /// + public T CaptureAny() + { + return Match.Create(value => this.EvaluateParameter(value), () => It.IsAny()); + } + + /// + /// Gets the parameter to use in the setup expression. + /// + public T Capture(Expression> match) + { + var matchDelegate = match.Compile(); + return Match.Create(value => matchDelegate.Invoke(value) && this.EvaluateParameter(value), () => It.Is(match)); + } + + private bool EvaluateParameter(T item) + { + var capturedValue = this.captureCallback.Invoke(item); + this.capturedValues.Add(capturedValue); + + return true; + } + + private static Func CreateCaptureCallback(Action action) + { + return x => + { + action.Invoke(x); + return x; + }; + } + } +} \ No newline at end of file diff --git a/Source/Moq.csproj b/Source/Moq.csproj index 4e72af310..118e885db 100644 --- a/Source/Moq.csproj +++ b/Source/Moq.csproj @@ -42,6 +42,7 @@ + @@ -64,6 +65,7 @@ True IReturns.tt + diff --git a/UnitTests/CaptureFixture.cs b/UnitTests/CaptureFixture.cs new file mode 100644 index 000000000..942aa1515 --- /dev/null +++ b/UnitTests/CaptureFixture.cs @@ -0,0 +1,40 @@ +using System.Collections.Generic; +using Xunit; + +namespace Moq.Tests +{ + public class CaptureFixture + { + [Fact] + public void CanCaptureAnyParameterInCollection() + { + var items = new List(); + var mock = new Mock(); + mock.Setup(x => x.DoSomething(Capture.In(items))); + + mock.Object.DoSomething("Hello!"); + + var expectedValues = new List { "Hello!" }; + Assert.Equal(expectedValues, items); + } + + [Fact] + public void CanCaptureSpecificParameterInCollection() + { + var items = new List(); + var mock = new Mock(); + mock.Setup(x => x.DoSomething(Capture.In(items, p => p.StartsWith("W")))); + + mock.Object.DoSomething("Hello!"); + mock.Object.DoSomething("World!"); + + var expectedValues = new List { "World!" }; + Assert.Equal(expectedValues, items); + } + + public interface IFoo + { + void DoSomething(string s); + } + } +} \ No newline at end of file diff --git a/UnitTests/MockCaptureFixture.cs b/UnitTests/MockCaptureFixture.cs new file mode 100644 index 000000000..73fee6223 --- /dev/null +++ b/UnitTests/MockCaptureFixture.cs @@ -0,0 +1,125 @@ +using System; +using System.Collections.Generic; +using Xunit; + +namespace Moq.Tests +{ + public class MockCaptureFixture + { + [Fact] + public void CanCaptureParameter() + { + var capture = new MockCapture(); + var mock = new Mock(); + mock.Setup(x => x.DoSomething(capture.CaptureAny())); + + mock.Object.DoSomething("Hello!"); + + var expectedValues = new List { "Hello!" }.AsReadOnly(); + Assert.Equal(expectedValues, capture.Values); + } + + [Fact] + public void CanCaptureMultipleParameters() + { + var capture = new MockCapture(); + var mock = new Mock(); + mock.Setup(x => x.DoSomething(capture.CaptureAny())); + + mock.Object.DoSomething("Hello!"); + mock.Object.DoSomething("World!"); + + var expectedValues = new List { "Hello!", "World!" }.AsReadOnly(); + Assert.Equal(expectedValues, capture.Values); + } + + [Fact] + public void CanCaptureSpecificParameter() + { + var capture = new MockCapture(); + var mock = new Mock(); + mock.Setup(x => x.DoSomething(capture.Capture(p => p.StartsWith("W")))); + + mock.Object.DoSomething("Hello!"); + mock.Object.DoSomething("World!"); + + var expectedValues = new List { "World!" }.AsReadOnly(); + Assert.Equal(expectedValues, capture.Values); + } + + [Fact] + public void CanCaptureLastParameter() + { + var capture = new MockCapture(); + var mock = new Mock(); + mock.Setup(x => x.DoSomething(capture.CaptureAny())); + + mock.Object.DoSomething("Hello!"); + mock.Object.DoSomething("World!"); + + Assert.Equal("World!", capture.LastValue); + } + + [Fact] + public void CanRunActionCallback() + { + var capture = new MockCapture(x => x.Value = 42); + var mock = new Mock(); + mock.Setup(x => x.DoSomething(capture.CaptureAny())); + + var param = new FooParam(0); + mock.Object.DoSomething(param); + + Assert.Equal(42, param.Value); + Assert.Equal(42, capture.LastValue.Value); + } + + [Fact] + public void CanRunFuncCallback() + { + var capture = new MockCapture(x => new FooParam(x.Value)); + var mock = new Mock(); + mock.Setup(x => x.DoSomething(capture.CaptureAny())); + + var param = new FooParam(41); + mock.Object.DoSomething(param); + param.Value = 42; + mock.Object.DoSomething(param); + param.Value = 0; + + var expectedValues = new List { new FooParam(41), new FooParam(42) }.AsReadOnly(); + Assert.Equal(expectedValues, capture.Values); + } + + public interface IFoo + { + void DoSomething(string item); + void DoSomething(FooParam item); + } + + public class FooParam : IEquatable + { + public FooParam(int value) + { + Value = value; + } + + public int Value { get; set; } + + public bool Equals(FooParam other) + { + return other != null && Value == other.Value; + } + + public override bool Equals(object obj) + { + return Equals(obj as FooParam); + } + + public override int GetHashCode() + { + return Value; + } + } + } +} \ No newline at end of file diff --git a/UnitTests/Moq.Tests.csproj b/UnitTests/Moq.Tests.csproj index 4356c4210..4aa256781 100644 --- a/UnitTests/Moq.Tests.csproj +++ b/UnitTests/Moq.Tests.csproj @@ -55,9 +55,11 @@ + +