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

TUnit Support #202

Merged
merged 3 commits into from
Dec 20, 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
2 changes: 2 additions & 0 deletions src/Snapshooter.TUnit/AssemblyInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Snapshooter.TUnit.Tests")]
25 changes: 25 additions & 0 deletions src/Snapshooter.TUnit/Snapshooter.TUnit.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="$(CCResourceProjectProps)" Condition="Exists('$(CCResourceProjectProps)')" />

<PropertyGroup>
<TargetFrameworks>net8.0</TargetFrameworks>
<AssemblyName>Snapshooter.TUnit</AssemblyName>
<RootNamespace>Snapshooter.TUnit</RootNamespace>
<PackageId>Snapshooter.TUnit</PackageId>
<Description>
TUnit Snapshooter is a flexible snapshot testing tool for .Net unit tests with TUnit.
It creates and asserts snapshots (json format) within TUnit unit tests.
</Description>
<IsTestProject>false</IsTestProject>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\Snapshooter\Snapshooter.csproj" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="TUnit.Assertions" Version="0.3.20" />
<PackageReference Include="TUnit.Core" Version="0.3.20" />
</ItemGroup>

</Project>
397 changes: 397 additions & 0 deletions src/Snapshooter.TUnit/Snapshot.cs

Large diffs are not rendered by default.

131 changes: 131 additions & 0 deletions src/Snapshooter.TUnit/SnapshotExtension.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
using System;

namespace Snapshooter.TUnit
{
public static class SnapshotExtension
{
/// <summary>
/// Creates a json snapshot of the given object and compares it with the
/// already existing snapshot of the test.
/// If no snapshot exists, a new snapshot will be created from the current result
/// and saved under a certain file path, which will shown within the test message.
/// </summary>
/// <param name="currentResult">The object to match.</param>
/// <param name="matchOptions">
/// Additional compare actions, which can be applied during the snapshot comparison
/// </param>
public static void MatchSnapshot(
this object currentResult,
Func<MatchOptions, MatchOptions> matchOptions = null)
{
var cleanedObject = currentResult.RemoveUnwantedWrappers();
Snapshot.Match(cleanedObject, matchOptions);
}

/// <summary>
/// Creates a json snapshot of the given object and compares it with the
/// already existing snapshot of the test.
/// If no snapshot exists, a new snapshot will be created from the current result
/// and saved under a certain file path, which will shown within the test message.
/// </summary>
/// <param name="currentResult">The object to match.</param>
/// <param name="snapshotNameExtension">
/// The snapshot name extension will extend the generated snapshot name with
/// this given extensions. It can be used to make a snapshot name even more
/// specific.
/// Example:
/// Generated Snapshotname = 'NumberAdditionTest'
/// Snapshot name extension = '5', '6', 'Result', '11'
/// Result: 'NumberAdditionTest_5_6_Result_11'
/// </param>
/// <param name="matchOptions">
/// Additional compare actions, which can be applied during the snapshot comparison
/// </param>
public static void MatchSnapshot(
this object currentResult,
SnapshotNameExtension snapshotNameExtension,
Func<MatchOptions, MatchOptions> matchOptions = null)
{
var cleanedObject = currentResult.RemoveUnwantedWrappers();
Snapshot.Match(cleanedObject, snapshotNameExtension, matchOptions);
}

/// <summary>
/// Creates a json snapshot of the given object and compares it with the
/// already existing snapshot of the test.
/// If no snapshot exists, a new snapshot will be created from the current result
/// and saved under a certain file path, which will shown within the test message.
/// </summary>
/// <param name="currentResult">The object to match.</param>
/// <param name="snapshotName">
/// The name of the snapshot. If not set, then the snapshotname
/// will be evaluated automatically from the TUnit test name.
/// </param>
/// <param name="matchOptions">
/// Additional compare actions, which can be applied during the snapshot comparison
/// </param>
public static void MatchSnapshot(
this object currentResult,
string snapshotName,
Func<MatchOptions, MatchOptions> matchOptions = null)
{
var cleanedObject = currentResult.RemoveUnwantedWrappers();
Snapshot.Match(cleanedObject, snapshotName, matchOptions);
}

/// <summary>
/// Creates a json snapshot of the given object and compares it with the
/// already existing snapshot of the test.
/// If no snapshot exists, a new snapshot will be created from the current result
/// and saved under a certain file path, which will shown within the test message.
/// </summary>
/// <param name="currentResult">The object to match.</param>
/// <param name="snapshotName">
/// The name of the snapshot. If not set, then the snapshotname
/// will be evaluated automatically from the TUnit test name.
/// </param>
/// <param name="snapshotNameExtension">
/// The snapshot name extension will extend the generated snapshot name with
/// this given extensions. It can be used to make a snapshot name even more
/// specific.
/// Example:
/// Generated Snapshotname = 'NumberAdditionTest'
/// Snapshot name extension = '5', '6', 'Result', '11'
/// Result: 'NumberAdditionTest_5_6_Result_11'
/// </param>
/// <param name="matchOptions">
/// Additional compare actions, which can be applied during the snapshot comparison.
/// </param>
public static void MatchSnapshot(
this object currentResult,
string snapshotName,
SnapshotNameExtension snapshotNameExtension,
Func<MatchOptions, MatchOptions> matchOptions = null)
{
var cleanedObject = currentResult.RemoveUnwantedWrappers();
Snapshot.Match(cleanedObject, snapshotName, snapshotNameExtension, matchOptions);
}

/// <summary>
/// Creates a json snapshot of the given object and compares it with the
/// already existing snapshot of the test.
/// If no snapshot exists, a new snapshot will be created from the current result
/// and saved under a certain file path, which will shown within the test message.
/// </summary>
/// <param name="currentResult">The object to match.</param>
/// <param name="snapshotFullName">
/// The full name of a snapshot with folder and file name.
/// To get a SnapshotFullName use Snapshot.FullName(). </param>
/// <param name="matchOptions">
/// Additional compare actions, which can be applied during the snapshot comparison.
/// </param>
public static void MatchSnapshot(
this object currentResult,
SnapshotFullName snapshotFullName,
Func<MatchOptions, MatchOptions> matchOptions = null)
{
var cleanedObject = currentResult.RemoveUnwantedWrappers();
Snapshot.Match(cleanedObject, snapshotFullName, matchOptions);
}
}
}
26 changes: 26 additions & 0 deletions src/Snapshooter.TUnit/TUnitAssert.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using Snapshooter.Core;
using TUnit.Assertions.Extensions;
using TAssert = TUnit.Assertions.Assert;

namespace Snapshooter.TUnit
{
/// <summary>
/// The NunitAssert instance compares two strings with the TUnit assert utility.
/// </summary>
public class TUnitAssert : IAssert
{
/// <summary>
/// Asserts the expected snapshot and the actual snapshot
/// with the TUnit assert utility.
/// </summary>
/// <param name="expectedSnapshot">The expected snapshot.</param>
/// <param name="actualSnapshot">The actual snapshot.</param>
public void Assert(string expectedSnapshot, string actualSnapshot)
{
// TUnit assertions use an async syntax but this interface is restricted to synchronous calls
#pragma warning disable TUnitAssertions0002
TAssert.That(actualSnapshot).IsEqualTo(expectedSnapshot).GetAwaiter().GetResult();
#pragma warning restore TUnitAssertions0002
}
}
}
108 changes: 108 additions & 0 deletions src/Snapshooter.TUnit/TUnitSnapshotFullNameReader.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using Snapshooter.Core;
using Snapshooter.Exceptions;
using Snapshooter.Extensions;
using TUnit.Core;

namespace Snapshooter.TUnit
{
/// <summary>
/// A TUnit snapshot full name reader is responsible to get the information
/// for the snapshot file from a TUnit test.
/// </summary>
public class TUnitSnapshotFullNameReader : ISnapshotFullNameReader
{
/// <summary>
/// Evaluates the snapshot full name information.
/// </summary>
/// <returns>The full name of the snapshot.</returns>
public SnapshotFullName ReadSnapshotFullName()
{
SnapshotFullName snapshotFullName = null;
StackFrame[] stackFrames = new StackTrace(true).GetFrames();
foreach (StackFrame stackFrame in stackFrames)
{
MethodBase method = stackFrame.GetMethod();
if (IsTUnitTestMethod(method))
{
snapshotFullName = new SnapshotFullName(
GetCurrentSnapshotName(),
stackFrame.GetFileName().GetDirectoryName());

break;
}

MethodBase asyncMethod = EvaluateAsynchronousMethodBase(method);
if (IsTUnitTestMethod(asyncMethod))
{
snapshotFullName = new SnapshotFullName(
GetCurrentSnapshotName(),
stackFrame.GetFileName().GetDirectoryName());

break;
}
}

if (snapshotFullName == null)
{
throw new SnapshotTestException(
"The snapshot full name could not be evaluated. " +
"This error can occur, if you use the snapshot match " +
"within a async test helper child method. To solve this issue, " +
"use the Snapshot.FullName directly in the unit test to " +
"get the snapshot name, then reach this name to your " +
"Snapshot.Match method.");
}

snapshotFullName = LiveUnitTestingDirectoryResolver
.CheckForSession(snapshotFullName);

return snapshotFullName;
}

private static bool IsTUnitTestMethod(MemberInfo method)
{
return method?.GetCustomAttributes(typeof(TestAttribute)).Any() ?? false;
}

private static MethodBase EvaluateAsynchronousMethodBase(MemberInfo method)
{
Type methodDeclaringType = method?.DeclaringType;
Type classDeclaringType = methodDeclaringType?.DeclaringType;

MethodInfo actualMethodInfo = null;
if (classDeclaringType != null)
{
IEnumerable<MethodInfo> selectedMethodInfos =
from methodInfo in classDeclaringType.GetMethods()
let stateMachineAttribute = methodInfo
.GetCustomAttribute<AsyncStateMachineAttribute>()
where stateMachineAttribute != null &&
stateMachineAttribute.StateMachineType == methodDeclaringType
select methodInfo;

actualMethodInfo = selectedMethodInfos.SingleOrDefault();
}

return actualMethodInfo;

}

private static string GetCurrentSnapshotName()
{
TestContext currentTestContext = TestContext.Current!;

var typeName = currentTestContext.TestDetails.ClassType.Name;
var methodName = currentTestContext.TestDetails.TestName;
var parameters = SnapshotNameExtension.Create(currentTestContext.TestDetails.TestMethodArguments).ToParamsString();

return $"{typeName}.{methodName}{parameters}";
}
}
}
29 changes: 29 additions & 0 deletions src/Snapshooter.sln
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Snapshooter.Tests.Data", ".
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Snapshooter.Xunit.Tests", "..\test\Snapshooter.Xunit.Tests\Snapshooter.Xunit.Tests.csproj", "{3C7A875E-7B9C-45E6-93E1-E952F08758B4}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Snapshooter.TUnit", "Snapshooter.TUnit\Snapshooter.TUnit.csproj", "{A17E038B-5283-4784-A302-BC3D64E5764A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Snapshooter.TUnit.Tests", "..\test\Snapshooter.TUnit.Tests\Snapshooter.TUnit.Tests.csproj", "{8B65FDBB-A430-406E-8992-1B4474D99358}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -198,6 +202,30 @@ Global
{3C7A875E-7B9C-45E6-93E1-E952F08758B4}.Release|x64.Build.0 = Release|Any CPU
{3C7A875E-7B9C-45E6-93E1-E952F08758B4}.Release|x86.ActiveCfg = Release|Any CPU
{3C7A875E-7B9C-45E6-93E1-E952F08758B4}.Release|x86.Build.0 = Release|Any CPU
{A17E038B-5283-4784-A302-BC3D64E5764A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A17E038B-5283-4784-A302-BC3D64E5764A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A17E038B-5283-4784-A302-BC3D64E5764A}.Debug|x64.ActiveCfg = Debug|Any CPU
{A17E038B-5283-4784-A302-BC3D64E5764A}.Debug|x64.Build.0 = Debug|Any CPU
{A17E038B-5283-4784-A302-BC3D64E5764A}.Debug|x86.ActiveCfg = Debug|Any CPU
{A17E038B-5283-4784-A302-BC3D64E5764A}.Debug|x86.Build.0 = Debug|Any CPU
{A17E038B-5283-4784-A302-BC3D64E5764A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A17E038B-5283-4784-A302-BC3D64E5764A}.Release|Any CPU.Build.0 = Release|Any CPU
{A17E038B-5283-4784-A302-BC3D64E5764A}.Release|x64.ActiveCfg = Release|Any CPU
{A17E038B-5283-4784-A302-BC3D64E5764A}.Release|x64.Build.0 = Release|Any CPU
{A17E038B-5283-4784-A302-BC3D64E5764A}.Release|x86.ActiveCfg = Release|Any CPU
{A17E038B-5283-4784-A302-BC3D64E5764A}.Release|x86.Build.0 = Release|Any CPU
{8B65FDBB-A430-406E-8992-1B4474D99358}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8B65FDBB-A430-406E-8992-1B4474D99358}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8B65FDBB-A430-406E-8992-1B4474D99358}.Debug|x64.ActiveCfg = Debug|Any CPU
{8B65FDBB-A430-406E-8992-1B4474D99358}.Debug|x64.Build.0 = Debug|Any CPU
{8B65FDBB-A430-406E-8992-1B4474D99358}.Debug|x86.ActiveCfg = Debug|Any CPU
{8B65FDBB-A430-406E-8992-1B4474D99358}.Debug|x86.Build.0 = Debug|Any CPU
{8B65FDBB-A430-406E-8992-1B4474D99358}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8B65FDBB-A430-406E-8992-1B4474D99358}.Release|Any CPU.Build.0 = Release|Any CPU
{8B65FDBB-A430-406E-8992-1B4474D99358}.Release|x64.ActiveCfg = Release|Any CPU
{8B65FDBB-A430-406E-8992-1B4474D99358}.Release|x64.Build.0 = Release|Any CPU
{8B65FDBB-A430-406E-8992-1B4474D99358}.Release|x86.ActiveCfg = Release|Any CPU
{8B65FDBB-A430-406E-8992-1B4474D99358}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -211,6 +239,7 @@ Global
{616F100E-A562-4E17-805A-8755B9D4D1AA} = {F9DFF684-4ACF-45E4-B23E-E8928DE0C9FE}
{A9A09C8D-E9D1-45CC-80F1-3C8DDF8F2600} = {F9DFF684-4ACF-45E4-B23E-E8928DE0C9FE}
{3C7A875E-7B9C-45E6-93E1-E952F08758B4} = {F9DFF684-4ACF-45E4-B23E-E8928DE0C9FE}
{8B65FDBB-A430-406E-8992-1B4474D99358} = {F9DFF684-4ACF-45E4-B23E-E8928DE0C9FE}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {2F64A2AB-ACA2-4E2D-B7E2-B87E93C66A24}
Expand Down
8 changes: 8 additions & 0 deletions test/Snapshooter.Json.Tests/Snapshooter.Json.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@
<ProjectReference Include="..\Snapshooter.Tests.Data\Snapshooter.Tests.Data.csproj" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="xunit" Version="2.4.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>

<ItemGroup>
<None Update="**\__snapshots__\*.snap">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
Expand Down
21 changes: 21 additions & 0 deletions test/Snapshooter.TUnit.Tests/Snapshooter.TUnit.Tests.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="$(CCTestProjectProps)" Condition="Exists('$(CCTestProjectProps)')" />

<PropertyGroup>
<TargetFrameworks>net8.0</TargetFrameworks>
<AssemblyName>Snapshooter.TUnit.Tests</AssemblyName>
<RootNamespace>Snapshooter.TUnit.Tests</RootNamespace>
<IsTestProject>true</IsTestProject>
<LangVersion>latest</LangVersion>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\Snapshooter.TUnit\Snapshooter.TUnit.csproj" />
<ProjectReference Include="..\Snapshooter.Tests.Data\Snapshooter.Tests.Data.csproj" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="TUnit" Version="0.3.20" />
</ItemGroup>

</Project>
Loading
Loading