Skip to content

Commit

Permalink
Add An<T> for argument constraints and specs
Browse files Browse the repository at this point in the history
  • Loading branch information
thomaslevesque committed Mar 5, 2021
1 parent 6783267 commit 01adfb2
Show file tree
Hide file tree
Showing 2 changed files with 197 additions and 0 deletions.
44 changes: 44 additions & 0 deletions src/FakeItEasy/An.of.T.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
namespace FakeItEasy
{
using System;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;

/// <summary>
/// Provides an API entry point for constraining arguments of fake object calls.
/// </summary>
/// <typeparam name="T">The type of argument to validate.</typeparam>
/// <remarks>This is a synonym of <see cref="A{T}"/>, to enable proper grammar when the type of T starts with a vowel.</remarks>
[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = nameof(A), Justification = "It is spelled correctly.")]
public static class An<T>
{
/// <summary>
/// Gets an argument constraint object that will be used to constrain a method call argument.
/// </summary>
[SuppressMessage("Microsoft.Design", "CA1000:DoNotDeclareStaticMembersOnGenericTypes", Justification = "This is a special case where the type parameter acts as an entry point into the fluent api.")]
public static INegatableArgumentConstraintManager<T> That =>
ServiceLocator.Resolve<IArgumentConstraintManagerFactory>().Create<T>();

/// <summary>
/// Gets a constraint that considers any value of an argument as valid.
/// </summary>
/// <remarks>This is a shortcut for the "Ignored"-property.</remarks>
[SuppressMessage("Microsoft.Design", "CA1000:DoNotDeclareStaticMembersOnGenericTypes", Justification = "This is a special case where the type parameter acts as an entry point into the fluent api.")]
[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "_", Justification = "Some trickery is allowed, isn't it?")]
[SuppressMessage("Microsoft.Naming", "CA1707:IdentifiersShouldNotContainUnderscores", Justification = "But it's kinda cool right?")]
[EditorBrowsable(EditorBrowsableState.Never)]
[CLSCompliant(false)]
#pragma warning disable SA1300 // Element must begin with upper-case letter
public static T _ => Ignored;
#pragma warning restore SA1300 // Element must begin with upper-case letter

/// <summary>
/// Gets a constraint that considers any value of an argument as valid.
/// </summary>
[SuppressMessage("Microsoft.Design", "CA1000:DoNotDeclareStaticMembersOnGenericTypes", Justification = "This is a special case where the type parameter acts as an entry point into the fluent api.")]
public static T Ignored
{
get { return That.Matches(x => true, x => x.Write(nameof(Ignored))); }
}
}
}
153 changes: 153 additions & 0 deletions tests/FakeItEasy.Specs/AnArgumentConstraintSpecs.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
namespace FakeItEasy.Specs
{
using FluentAssertions;
using Xbehave;

public static class AnArgumentConstraintSpecs
{
public interface IFoo
{
int Bar(int x);

int Bar(Apple apple);
}

[Scenario]
public static void AnIntIgnored(IFoo fake, int result)
{
"Given a fake"
.x(() => fake = A.Fake<IFoo>());

"And a method of the fake configured with the An<int>.Ignored argument constraint"
.x(() => A.CallTo(() => fake.Bar(An<int>.Ignored)).Returns(42));

"When the configured method is called"
.x(() => result = fake.Bar(123));

"Then it returns the configured value"
.x(() => result.Should().Be(42));
}

[Scenario]
public static void AnIntUnderscore(IFoo fake, int result)
{
"Given a fake"
.x(() => fake = A.Fake<IFoo>());

"And a method of the fake configured with the An<int>._ argument constraint"
.x(() => A.CallTo(() => fake.Bar(An<int>._)).Returns(42));

"When the configured method is called"
.x(() => result = fake.Bar(123));

"Then it returns the configured value"
.x(() => result.Should().Be(42));
}

[Scenario]
public static void AnIntThatWithMatchingArgument(IFoo fake, int result)
{
"Given a fake"
.x(() => fake = A.Fake<IFoo>());

"And a method of the fake configured with the An<int>.That argument constraint"
.x(() => A.CallTo(() => fake.Bar(An<int>.That.Matches(i => i % 2 == 1))).Returns(42));

"When the configured method is called with an argument that matches the constraint"
.x(() => result = fake.Bar(123));

"Then it returns the configured value"
.x(() => result.Should().Be(42));
}

[Scenario]
public static void AnIntThatWithNonMatchingArgument(IFoo fake, int result)
{
"Given a fake"
.x(() => fake = A.Fake<IFoo>());

"And a method of the fake configured with the An<int>.That argument constraint"
.x(() => A.CallTo(() => fake.Bar(An<int>.That.Matches(i => i % 2 == 1))).Returns(42));

"When the configured method is called with an argument that doesn't match the constraint"
.x(() => result = fake.Bar(12));

"Then it returns the default value"
.x(() => result.Should().Be(0));
}

[Scenario]
public static void AnAppleIgnored(IFoo fake, int result)
{
"Given a fake"
.x(() => fake = A.Fake<IFoo>());

"And a method of the fake configured with the An<Apple>.Ignored argument constraint"
.x(() => A.CallTo(() => fake.Bar(An<Apple>.Ignored)).Returns(42));

"When the configured method is called"
.x(() => result = fake.Bar(new Apple("Red")));

"Then it returns the configured value"
.x(() => result.Should().Be(42));
}

[Scenario]
public static void AnAppleUnderscore(IFoo fake, int result)
{
"Given a fake"
.x(() => fake = A.Fake<IFoo>());

"And a method of the fake configured with the An<Apple>._ argument constraint"
.x(() => A.CallTo(() => fake.Bar(An<Apple>._)).Returns(42));

"When the configured method is called"
.x(() => result = fake.Bar(new Apple("Red")));

"Then it returns the configured value"
.x(() => result.Should().Be(42));
}

[Scenario]
public static void AnAppleThatWithMatchingArgument(IFoo fake, int result)
{
"Given a fake"
.x(() => fake = A.Fake<IFoo>());

"And a method of the fake configured with the An<Apple>.That argument constraint"
.x(() => A.CallTo(() => fake.Bar(An<Apple>.That.Matches(a => a.Color == "Red"))).Returns(42));

"When the configured method is called with an argument that matches the constraint"
.x(() => result = fake.Bar(new Apple("Red")));

"Then it returns the configured value"
.x(() => result.Should().Be(42));
}

[Scenario]
public static void AnAppleThatNonWithMatchingArgument(IFoo fake, int result)
{
"Given a fake"
.x(() => fake = A.Fake<IFoo>());

"And a method of the fake configured with the An<Apple>.That argument constraint"
.x(() => A.CallTo(() => fake.Bar(An<Apple>.That.Matches(a => a.Color == "Red"))).Returns(42));

"When the configured method is called with an argument that doesn't match the constraint"
.x(() => result = fake.Bar(new Apple("Green")));

"Then it returns the default value"
.x(() => result.Should().Be(0));
}

public class Apple
{
public Apple(string color)
{
this.Color = color;
}

public string Color { get; }
}
}
}

0 comments on commit 01adfb2

Please sign in to comment.