Skip to content
This repository was archived by the owner on Jun 30, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Add new ways of verifying mocks, with syntax and fluent APIs
Just like for Arg, Raise and Setup, there is now a Syntax-based Verify which
allows much more succinct code. With the following using at the top:

```
using static Moq.Syntax;
```

You can now perform verifications on a mock as follows:

```
// Verify all "verifiable" calls, i.e. those with a Once/Never/etc. setup.
Verify(calculator);

// Verify a specific invocation was made at least once
Verify(calculator).Add(2, 3);

// Verify it was never called
Verify(calculator).Add(1, 1).Never();

// Verify it was called exactly once
Verify(calculator).Add(1, 1).Once();
```

Verifiable setups can now just add `Once`, `Never` and `Exactly` directly on
the setup too (unlike v4 which only had `Verifiable()` which meant `AtLeastOnce`)

```
var calculator = Mock.Of<Calculator>();

// The given call is expected only once
calculator.Add(2, 3).Returns(5).Once();

// The given call is never expected
calculator.Add(-1, -1).Never();

// The given call is expected at least once. Equivalent to v4 Verifiable
calculator.Add(1, 1).AtLeastOnce();

// A single call to Verify will verify all the above
Verify(calculator);

// Also supported, non-syntax version
Verify.Called(calculator);
// And instance version (via extension method backwards compatible)
calculator.Verify();
```

A non-syntax version is also provided, which is basically called from the
syntax one:

```
// Verify all "verifiable" calls, i.e. those with a Once/Never/etc. setup.
Verify.Called(calculator);

// Verify a specific invocation was made at least once
Verify.Called(calculator).Add(2, 3);

// Verify it was never called
Verify.Called(calculator).Add(1, 1).Never();

// Or alternative syntax depending on style preference
Verify.NotCalled(calculator).Add(2, 3);
```

A mechanism in the SDK for cloning mocks was added, so that they can be
"frozen" and tweaked independently from the original mock (i.e. for verification
purposes).

As part of cleaning up a mock's behaviors for cloning and verification, we took
the chance to split the former `MockTrackingBehavior` (which was doing two things)
into `MockContextBehavior` (the one that sets the `MockContext` static properties)
and `MockRecordingBehavior` (which does the actual invocation recording). This
allows getting a cloned mock that does not record calls, for example, for verifying
purposes or diagnostics, say. This could be used to also clone on debugger inspection,
to allow preserving the actual calls instead of having the inspection to modify the
actual calls (which is what happens typically by default in all mocking libraries).
  • Loading branch information
kzu committed Jul 22, 2019
commit 414f386dde1acf144d72ace46c5d17c0ac2ad79b
35 changes: 28 additions & 7 deletions src/Moq/Moq.Sdk.Tests/DefaultMockTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,35 @@ public void ThrowsIfNullStunt()
=> Assert.Throws<ArgumentNullException>(() => new DefaultMock(null));

[Fact]
public void AddsMockTrackingBehavior()
public void AddsMockContextBehavior()
{
var mock = new DefaultMock(new FakeStunt());

Assert.Collection(mock.Behaviors, x => Assert.IsType<MockTrackingBehavior>(x));
Assert.Contains(mock.Behaviors, x => x is MockContextBehavior);
}

[Fact]
public void PreventsDuplicateMockTrackingBehavior()
public void AddsMockRecordingBehavior()
{
var mock = new DefaultMock(new FakeStunt());

Assert.Throws<InvalidOperationException>(() => mock.Behaviors.Add(new MockTrackingBehavior()));
Assert.Contains(mock.Behaviors, x => x is MockRecordingBehavior);
}

[Fact]
public void PreventsDuplicateMockContextBehavior()
{
var mock = new DefaultMock(new FakeStunt());

Assert.Throws<InvalidOperationException>(() => mock.Behaviors.Add(new MockContextBehavior()));
}

[Fact]
public void PreventsDuplicateMockRecordingBehavior()
{
var mock = new DefaultMock(new FakeStunt());

Assert.Throws<InvalidOperationException>(() => mock.Behaviors.Add(new MockRecordingBehavior()));
}

[Fact]
Expand All @@ -40,33 +56,38 @@ public void TrackMockBehaviors()
new MethodInvocation(stunt, typeof(FakeStunt).GetMethod("Do")),
Array.Empty<IArgumentMatcher>());

var initialBehaviors = stunt.Behaviors.Count;
var behavior = new MockBehaviorPipeline(setup);

stunt.AddBehavior(behavior);
stunt.AddBehavior(new DelegateStuntBehavior((m, n) => n().Invoke(m, n)));
Assert.Equal(3, stunt.Behaviors.Count);
Assert.Equal(initialBehaviors + 2, stunt.Behaviors.Count);

Assert.Single(stunt.Mock.Setups);
Assert.Same(behavior, stunt.Mock.GetPipeline(setup));

stunt.Behaviors.Remove(behavior);

Assert.Equal(2, stunt.Behaviors.Count);
Assert.Equal(initialBehaviors + 1, stunt.Behaviors.Count);
Assert.Empty(stunt.Mock.Setups);
}

[Fact]
public void AddPipelineForSetupIfMissing()
{
var stunt = new FakeStunt();
// Forces initialization of the default mock.
Assert.NotNull(stunt.Mock);

var initialBehaviors = stunt.Behaviors.Count;
var setup = new MockSetup(
new MethodInvocation(stunt, typeof(FakeStunt).GetMethod("Do")),
Array.Empty<IArgumentMatcher>());

var behavior = stunt.Mock.GetPipeline(setup);

Assert.NotNull(behavior);
Assert.Equal(2, stunt.Behaviors.Count);
Assert.Equal(initialBehaviors + 1, stunt.Behaviors.Count);
Assert.Single(stunt.Mock.Setups);
}

Expand Down
4 changes: 4 additions & 0 deletions src/Moq/Moq.Sdk.Tests/Fakes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ public class FakeSetup : IMockSetup

public IArgumentMatcher[] Matchers { get; set; } = new IArgumentMatcher[0];

public Times? Occurrence { get; set; }

public StateBag State { get; } = new StateBag();

IMethodInvocation IMockSetup.Invocation => Invocation;

public bool Equals(IMockSetup other) => base.Equals(other);
Expand Down
6 changes: 3 additions & 3 deletions src/Moq/Moq.Sdk.Tests/MockBehaviorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public void ExecutesAnonymousBehavior()
[Fact]
public void RecordsInvocation()
{
var behavior = new MockTrackingBehavior();
var behavior = new MockRecordingBehavior();
var mock = new Mocked();

behavior.Execute(new MethodInvocation(mock, typeof(object).GetMethod(nameof(object.ToString))),
Expand All @@ -46,12 +46,12 @@ public void RecordsInvocation()
[Fact]
public void ThrowsForNonIMocked()
{
var behavior = new MockTrackingBehavior();
var behavior = new MockRecordingBehavior();

Assert.Throws<ArgumentException>(() => behavior.Execute(new MethodInvocation(
new object(),
typeof(Mocked).GetProperty(nameof(IMocked.Mock)).GetGetMethod()),
null));
() => (m, n) => m.CreateValueReturn(null)));
}

[Fact]
Expand Down
8 changes: 4 additions & 4 deletions src/Moq/Moq.Sdk.Tests/MockTrackingBehaviorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public void SetsCurrentInvocationAndSetup()
{
var target = new TrackingMock();
var invocation = new MethodInvocation(target, typeof(TrackingMock).GetMethod(nameof(TrackingMock.Do)));
var tracking = new MockTrackingBehavior();
var tracking = new MockContextBehavior();

Assert.NotNull(tracking.Execute(invocation, () => (m, n) => m.CreateValueReturn(null)));

Expand All @@ -27,9 +27,9 @@ public void RecordsInvocation()
{
var target = new TrackingMock();
var invocation = new MethodInvocation(target, typeof(TrackingMock).GetMethod(nameof(TrackingMock.Do)));
var tracking = new MockTrackingBehavior();
var recording = new MockRecordingBehavior();

Assert.NotNull(tracking.Execute(invocation, () => (m, n) => m.CreateValueReturn(null)));
Assert.NotNull(recording.Execute(invocation, () => (m, n) => m.CreateValueReturn(null)));

Assert.Single(target.Mock.Invocations);
}
Expand All @@ -39,7 +39,7 @@ public void SkipInvocationRecordingIfSetupScopeActive()
{
var target = new TrackingMock();
var invocation = new MethodInvocation(target, typeof(TrackingMock).GetMethod(nameof(TrackingMock.Do)));
var tracking = new MockTrackingBehavior();
var tracking = new MockContextBehavior();

using (new SetupScope())
{
Expand Down
215 changes: 215 additions & 0 deletions src/Moq/Moq.Sdk.Tests/TimesFixture.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
using System;
using Xunit;

namespace Moq.Sdk.Tests
{
public class TimesFixture
{
[Theory]
[InlineData(0, false)]
[InlineData(1, true)]
[InlineData(int.MaxValue, true)]
public void DefaultTimesRangesBetweenOneAndMaxValue(int count, bool verifies)
{
Assert.Equal(verifies, default(Times).Validate(count));
}

[Fact]
public void AtLeastOnceRangesBetweenOneAndMaxValue()
{
var target = Times.AtLeastOnce;

Assert.False(target.Validate(-1));
Assert.False(target.Validate(0));
Assert.True(target.Validate(1));
Assert.True(target.Validate(5));
Assert.True(target.Validate(int.MaxValue));
}

[Fact]
public void AtLeastThrowsIfTimesLessThanOne()
{
Assert.Throws<ArgumentOutOfRangeException>(() => Times.AtLeast(0));
Assert.Throws<ArgumentOutOfRangeException>(() => Times.AtLeast(-1));
}

[Fact]
public void AtLeastRangesBetweenTimesAndMaxValue()
{
var target = Times.AtLeast(10);

Assert.False(target.Validate(-1));
Assert.False(target.Validate(0));
Assert.False(target.Validate(9));
Assert.True(target.Validate(10));
Assert.True(target.Validate(int.MaxValue));
}

[Fact]
public void AtMostOnceRangesBetweenZeroAndOne()
{
var target = Times.AtMostOnce;

Assert.False(target.Validate(-1));
Assert.True(target.Validate(0));
Assert.True(target.Validate(1));
Assert.False(target.Validate(5));
Assert.False(target.Validate(int.MaxValue));
}

[Fact]
public void AtMostThrowsIfTimesLessThanZero()
{
Assert.Throws<ArgumentOutOfRangeException>(() => Times.AtMost(-1));
Assert.Throws<ArgumentOutOfRangeException>(() => Times.AtMost(-2));
}

[Fact]
public void AtMostRangesBetweenZeroAndTimes()
{
var target = Times.AtMost(10);

Assert.False(target.Validate(-1));
Assert.True(target.Validate(0));
Assert.True(target.Validate(6));
Assert.True(target.Validate(10));
Assert.False(target.Validate(11));
Assert.False(target.Validate(int.MaxValue));
}

[Fact]
public void ExactlyThrowsIfTimesLessThanZero()
{
Assert.Throws<ArgumentOutOfRangeException>(() => Times.Exactly(-1));
Assert.Throws<ArgumentOutOfRangeException>(() => Times.Exactly(-2));
}

[Fact]
public void ExactlyCheckExactTimes()
{
var target = Times.Exactly(10);

Assert.False(target.Validate(-1));
Assert.False(target.Validate(0));
Assert.False(target.Validate(9));
Assert.True(target.Validate(10));
Assert.False(target.Validate(11));
Assert.False(target.Validate(int.MaxValue));
}

[Fact]
public void NeverChecksZeroTimes()
{
var target = Times.Never;

Assert.False(target.Validate(-1));
Assert.True(target.Validate(0));
Assert.False(target.Validate(1));
Assert.False(target.Validate(int.MaxValue));
}

[Fact]
public void OnceChecksOneTime()
{
var target = Times.Once;

Assert.False(target.Validate(-1));
Assert.False(target.Validate(0));
Assert.True(target.Validate(1));
Assert.False(target.Validate(int.MaxValue));
}

public class Deconstruction
{
[Fact]
public void AtLeast_n_deconstructs_to_n_MaxValue()
{
const int n = 42;

var (from, to) = Times.AtLeast(n);
Assert.Equal(n, from);
Assert.Equal(int.MaxValue, to);
}

[Fact]
public void AtLeastOnce_deconstructs_to_1_MaxValue()
{
var (from, to) = Times.AtLeastOnce;
Assert.Equal(1, from);
Assert.Equal(int.MaxValue, to);
}

[Fact]
public void AtMost_n_deconstructs_to_0_n()
{
const int n = 42;

var (from, to) = Times.AtMost(n);
Assert.Equal(0, from);
Assert.Equal(n, to);
}

[Fact]
public void AtMostOnce_deconstructs_to_0_1()
{
var (from, to) = Times.AtMostOnce;
Assert.Equal(0, from);
Assert.Equal(1, to);
}

[Fact]
public void Exactly_n_deconstructs_to_n_n()
{
const int n = 42;
var (from, to) = Times.Exactly(n);
Assert.Equal(n, from);
Assert.Equal(n, to);
}

[Fact]
public void Once_deconstructs_to_1_1()
{
var (from, to) = Times.Once;
Assert.Equal(1, from);
Assert.Equal(1, to);
}

[Fact]
public void Never_deconstructs_to_0_0()
{
var (from, to) = Times.Never;
Assert.Equal(0, from);
Assert.Equal(0, to);
}
}

public class Equality
{
#pragma warning disable xUnit2000 // Constants and literals should be the expected argument
[Fact]
public void default_Equals_AtLeastOnce()
{
Assert.Equal(Times.AtLeastOnce, default(Times));
}
#pragma warning restore xUnit2000

[Fact]
public void default_GetHashCode_equals_AtLeastOnce_GetHashCode()
{
Assert.Equal(Times.AtLeastOnce.GetHashCode(), default(Times).GetHashCode());
}

[Fact]
public void Once_equals_Once()
{
Assert.Equal(Times.Once, Times.Once);
}

[Fact]
public void Once_equals_Exactly_1()
{
Assert.Equal(Times.Once, Times.Exactly(1));
}
}
}
}
Loading