Skip to content

Commit

Permalink
feat: add statistic assertions (#104)
Browse files Browse the repository at this point in the history
Implements #101
  • Loading branch information
vbreuss authored Apr 1, 2024
1 parent f44fec1 commit ff11899
Show file tree
Hide file tree
Showing 9 changed files with 1,218 additions and 2 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
namespace Testably.Abstractions.FluentAssertions;
using Testably.Abstractions.Testing.Statistics;

namespace Testably.Abstractions.FluentAssertions;

/// <summary>
/// Assertion extensions on <see cref="IFileSystem" />.
Expand Down Expand Up @@ -32,4 +34,11 @@ public static FileSystemInfoAssertions Should(this IFileSystemInfo? instance)
/// </summary>
public static FileSystemAssertions Should(this IFileSystem instance)
=> new(instance);

/// <summary>
/// Returns a <see cref="StatisticAssertions{TType}" /> object that can be used to
/// assert the current <see cref="IStatistics{TType}" />.
/// </summary>
public static StatisticAssertions<TType> Should<TType>(this IStatistics<TType>? instance)
=> new(instance);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
using System.Linq;
using Testably.Abstractions.Testing.Statistics;

namespace Testably.Abstractions.FluentAssertions;

/// <summary>
/// Assertions on <see cref="IFileInfo" />.
/// </summary>
public class StatisticAssertions<TType> :
ReferenceTypeAssertions<IStatistics?, StatisticAssertions<TType>>
{
/// <inheritdoc cref="ReferenceTypeAssertions{TSubject,TAssertions}.Identifier" />
protected override string Identifier => "statistics";

internal StatisticAssertions(IStatistics? instance)
: base(instance)
{
}

/// <summary>
/// Returns a <see cref="StatisticPropertyAssertions{TType,TAssertions}" /> object that can be used to assert that the
/// property named <paramref name="propertyName" /> was accessed a certain number of times.
/// </summary>
public StatisticPropertyAssertions<TType, StatisticAssertions<TType>> HaveAccessed(
string propertyName)
{
if (Subject == null)
{
return new StatisticPropertyAssertions<TType, StatisticAssertions<TType>>(this, propertyName);
}

return new StatisticPropertyAssertions<TType, StatisticAssertions<TType>>(this, propertyName,
Subject.Properties.Where(p => p.Name == propertyName));
}

/// <summary>
/// Returns a <see cref="StatisticMethodAssertions{TType,TAssertions}" /> object that can be used to assert that the
/// method named <paramref name="methodName" /> was called a certain number of times.
/// </summary>
public StatisticMethodAssertions<TType, StatisticAssertions<TType>> HaveCalled(
string methodName)
{
if (Subject == null)
{
return new StatisticMethodAssertions<TType, StatisticAssertions<TType>>(this, methodName);
}

return new StatisticMethodAssertions<TType, StatisticAssertions<TType>>(this, methodName,
Subject.Methods.Where(m => m.Name == methodName));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,242 @@
namespace Testably.Abstractions.FluentAssertions;

/// <summary>
/// Assertions on statistics.
/// </summary>
public abstract class StatisticAssertionsCount<TType, TAssertions>(TAssertions assertions)
where TAssertions : StatisticAssertions<TType>
{
/// <summary>
/// Flag indicating if the subject of the assertion is null.
/// </summary>
protected abstract bool IsSubjectNull { get; }

/// <summary>
/// The name of the statistic.
/// </summary>
protected abstract string StatisticName { get; }

/// <summary>
/// The type of the statistic.
/// </summary>
protected abstract string StatisticType { get; }

/// <summary>
/// The verb to interact with the statistic type.
/// </summary>
protected abstract string StatisticTypeVerb { get; }

/// <summary>
/// Asserts that the number of calls on the filtered methods/properties is at least <paramref name="count" /> times.
/// </summary>
public AndConstraint<TAssertions> AtLeast(int count,
string because = "", params object[] becauseArgs)
{
int actualCount = GetCount();
Execute.Assertion
.WithDefaultIdentifier("Statistic")
.BecauseOf(because, becauseArgs)
.ForCondition(!IsSubjectNull)
.FailWith("You can't assert a statistic if it is null.")
.Then
.ForCondition(actualCount >= count)
.FailWith(
$"Expected {StatisticType} `{StatisticName}` to be {StatisticTypeVerb} at least {CountToString(count)}{{reason}}, but it was {CountToString(actualCount)}.");

return new AndConstraint<TAssertions>(assertions);
}

/// <summary>
/// Asserts that the number of calls on the filtered methods/properties is at least 1.
/// </summary>
public AndConstraint<TAssertions> AtLeastOnce(
string because = "", params object[] becauseArgs)
{
int count = GetCount();
Execute.Assertion
.WithDefaultIdentifier("Statistic")
.BecauseOf(because, becauseArgs)
.ForCondition(!IsSubjectNull)
.FailWith("You can't assert a statistic if it is null.")
.Then
.ForCondition(count >= 1)
.FailWith(
$"Expected {StatisticType} `{StatisticName}` to be {StatisticTypeVerb} at least once{{reason}}, but it was {CountToString(count)}.");

return new AndConstraint<TAssertions>(assertions);
}

/// <summary>
/// Asserts that the number of calls on the filtered methods/properties is at least 2.
/// </summary>
public AndConstraint<TAssertions> AtLeastTwice(
string because = "", params object[] becauseArgs)
{
int count = GetCount();
Execute.Assertion
.WithDefaultIdentifier("Statistic")
.BecauseOf(because, becauseArgs)
.ForCondition(!IsSubjectNull)
.FailWith("You can't assert a statistic if it is null.")
.Then
.ForCondition(count >= 2)
.FailWith(
$"Expected {StatisticType} `{StatisticName}` to be {StatisticTypeVerb} at least twice{{reason}}, but it was {CountToString(count)}.");

return new AndConstraint<TAssertions>(assertions);
}

/// <summary>
/// Asserts that the number of calls on the filtered methods/properties is at most <paramref name="count" /> times.
/// </summary>
public AndConstraint<TAssertions> AtMost(int count,
string because = "", params object[] becauseArgs)
{
int actualCount = GetCount();
Execute.Assertion
.WithDefaultIdentifier("Statistic")
.BecauseOf(because, becauseArgs)
.ForCondition(!IsSubjectNull)
.FailWith("You can't assert a statistic if it is null.")
.Then
.ForCondition(actualCount <= count)
.FailWith(
$"Expected {StatisticType} `{StatisticName}` to be {StatisticTypeVerb} at most {CountToString(count)}{{reason}}, but it was {CountToString(actualCount)}.");

return new AndConstraint<TAssertions>(assertions);
}

/// <summary>
/// Asserts that the number of calls on the filtered methods/properties is at most 1.
/// </summary>
public AndConstraint<TAssertions> AtMostOnce(
string because = "", params object[] becauseArgs)
{
int count = GetCount();
Execute.Assertion
.WithDefaultIdentifier("Statistic")
.BecauseOf(because, becauseArgs)
.ForCondition(!IsSubjectNull)
.FailWith("You can't assert a statistic if it is null.")
.Then
.ForCondition(count <= 1)
.FailWith(
$"Expected {StatisticType} `{StatisticName}` to be {StatisticTypeVerb} at most once{{reason}}, but it was {CountToString(count)}.");

return new AndConstraint<TAssertions>(assertions);
}

/// <summary>
/// Asserts that the number of calls on the filtered methods/properties is at most 2.
/// </summary>
public AndConstraint<TAssertions> AtMostTwice(
string because = "", params object[] becauseArgs)
{
int count = GetCount();
Execute.Assertion
.WithDefaultIdentifier("Statistic")
.BecauseOf(because, becauseArgs)
.ForCondition(!IsSubjectNull)
.FailWith("You can't assert a statistic if it is null.")
.Then
.ForCondition(count <= 2)
.FailWith(
$"Expected {StatisticType} `{StatisticName}` to be {StatisticTypeVerb} at most twice{{reason}}, but it was {CountToString(count)}.");

return new AndConstraint<TAssertions>(assertions);
}

/// <summary>
/// Asserts that the number of calls on the filtered methods/properties is exactly <paramref name="count" /> times.
/// </summary>
public AndConstraint<TAssertions> Exactly(int count,
string because = "", params object[] becauseArgs)
{
int actualCount = GetCount();
Execute.Assertion
.WithDefaultIdentifier("Statistic")
.BecauseOf(because, becauseArgs)
.ForCondition(!IsSubjectNull)
.FailWith("You can't assert a statistic if it is null.")
.Then
.ForCondition(actualCount == count)
.FailWith(
$"Expected {StatisticType} `{StatisticName}` to be {StatisticTypeVerb} {CountToString(count)}{{reason}}, but it was {CountToString(actualCount)}.");

return new AndConstraint<TAssertions>(assertions);
}

/// <summary>
/// Asserts that the number of calls on the filtered methods/properties is 0 (zero).
/// </summary>
public AndConstraint<TAssertions> Never(
string because = "", params object[] becauseArgs)
{
int count = GetCount();
Execute.Assertion
.WithDefaultIdentifier("Statistic")
.BecauseOf(because, becauseArgs)
.ForCondition(!IsSubjectNull)
.FailWith("You can't assert a statistic if it is null.")
.Then
.ForCondition(count == 0)
.FailWith(
$"Expected {StatisticType} `{StatisticName}` to never be {StatisticTypeVerb}{{reason}}, but it was {CountToString(count)}.");

return new AndConstraint<TAssertions>(assertions);
}

/// <summary>
/// Asserts that the number of calls on the filtered methods/properties is 1.
/// </summary>
public AndConstraint<TAssertions> Once(
string because = "", params object[] becauseArgs)
{
int count = GetCount();
Execute.Assertion
.WithDefaultIdentifier("Statistic")
.BecauseOf(because, becauseArgs)
.ForCondition(!IsSubjectNull)
.FailWith("You can't assert a statistic if it is null.")
.Then
.ForCondition(count == 1)
.FailWith(
$"Expected {StatisticType} `{StatisticName}` to be {StatisticTypeVerb} once{{reason}}, but it was {CountToString(count)}.");

return new AndConstraint<TAssertions>(assertions);
}

/// <summary>
/// Asserts that the number of calls on the filtered methods/properties is 2.
/// </summary>
public AndConstraint<TAssertions> Twice(
string because = "", params object[] becauseArgs)
{
int count = GetCount();
Execute.Assertion
.WithDefaultIdentifier("Statistic")
.BecauseOf(because, becauseArgs)
.ForCondition(!IsSubjectNull)
.FailWith("You can't assert a statistic if it is null.")
.Then
.ForCondition(count == 2)
.FailWith(
$"Expected {StatisticType} `{StatisticName}` to be {StatisticTypeVerb} twice{{reason}}, but it was {CountToString(count)}.");

return new AndConstraint<TAssertions>(assertions);
}

/// <summary>
/// Get the count of matching statistic values.
/// </summary>
protected abstract int GetCount();

private static string CountToString(int count)
=> count switch
{
0 => "never",
1 => "once",
2 => "twice",
_ => $"{count} times"
};
}
Loading

0 comments on commit ff11899

Please sign in to comment.