Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add statistic assertions #104

Merged
merged 8 commits into from
Apr 1, 2024
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
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
Loading