CheckTestOutput 0.6.3

dotnet add package CheckTestOutput --version 0.6.3                
NuGet\Install-Package CheckTestOutput -Version 0.6.3                
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="CheckTestOutput" Version="0.6.3" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add CheckTestOutput --version 0.6.3                
#r "nuget: CheckTestOutput, 0.6.3"                
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install CheckTestOutput as a Cake Addin
#addin nuget:?package=CheckTestOutput&version=0.6.3

// Install CheckTestOutput as a Cake Tool
#tool nuget:?package=CheckTestOutput&version=0.6.3                

CheckTestOutput

A library for semi-manual test output verification. Asks you to git add the new output if it changed.

CheckTestOutput simply compares the test output with the last "accepted" file. When it differs, you get an error. It is compared with its version from git index and throws an exception if it does not match, prints a diff and writes a new version to the working tree. To accept the new version, you simply stage the changed file (git add ...). To inspect the differences, you use your favorite diff tool.

Although it's a nice idea that tests should verify if the results are correct by some smart logic, it not always possible/practical. For example, when testing a transpiler, it would come in handy to solve the halting problem. In such cases, you will end up with an Assert.Equal("some very long code including many \" \" and \n \n, super fun to read and maintain", generatedCode). This project just makes the long asserts less annoying.

Usage

It requires your project to be in git version control system (it works without git, but does not offer the simple stage-to-accept workflow). You can use any test framework you want, this thing just throws exceptions -- we'll use XUnit in the examples here. The OutputChecker constructor parameter specifies where are the output files (relative to the test file location - it uses C#/F# caller info attributes). In this case, it's ./testoutputs. The file name will be equal to the caller method name, in this case, SomeTest.TestString.txt.

This is how you check if simple string matches:

public class SomeTest
{
    OutputChecker check = new OutputChecker("testoutputs");
    [Fact]
    public void TestString()
    {
        string someData = DoSomeComputation();
        check.CheckString(someData);
    }
}

You can also check if a collection of lines matches:

[Fact]
public void TestLines()
{
    IEnumerable<string> someData = GetSomeResults().Select(a => a.ToString());
    check.CheckLines(someData);
}

Check if the object matches when it is serialized to JSON (using Newtonsoft.Json)

[Fact]
public void TestObject()
{
    PersonViewModel someData = GetDefaultPersonDetail();
    check.CheckJsonObject(someData);
}

To use more that one check in one test, you need to give them names (so they don't end up overriding themselves):

[Fact]
public void TestWithMultipleChecks()
{
    PersonViewModel person = GetDefaultPersonDetail();
    check.CheckString(someData.CurriculumVitae, checkName: "cv");
    check.CheckString(someData.Genome, checkName: "genome");
}

Alternatively, combine them into one anonymous JSON object. It's generally preferable when the string are short - too many tiny files are annoying:

[Fact]
public void TestObject()
{
    PersonViewModel person = GetDefaultPersonDetail();
    check.CheckJsonObject(new {
        fname = person.Name,
        lname = person.LastName,
        person.BirthDate
    });
}

The checkName parameter is also useful for tests with parameters ([Theory] in XUnit). For example, this way we could test a regular expression:

[Theory]
[InlineData("positive", "it's 12th January")]
[InlineData("negative", "the result is -1")]
[InlineData("zero", "there is 0% growth")]
public void IncrementNumbers(string checkName, string testString)
{
    var replacedString = Regex.Replace(testString, "-?\\d+", m => int.Parse(m.Value) + 1 + "");
    check.CheckLines(new [] {
        testString,
        " -> ",
        replacedString
    }, checkName);
}

The text files have .txt file extension by default, but it's easy to change:

[Fact]
public void GenerateSomeCsharpCode()
{
    string code = GimmeSourceCode();
    check.CheckString(code, fileExtension: "cs");
}

Just keep in mind that dotnet is going to treat these .cs files as part of source code unless you <Compile Remove="testoutputs/**.cs" /> them in the .csproj file.

Binary data can be checked using check.CheckBinary(byte[]) method. No diff is printed in that case, you have to use an external tool to diff binary files.

F#

CheckTestOutput is reasonably F# friendly, although it's written in C#:

open CheckTestOutput

let check = OutputChecker "testoutputs"

[<Fact>]
let ``Simple object processing - UseGenericUnion`` () =
    computeSomething 123 "456"
    |> string
    |> check.CheckString

    // or if you need checkName
    check.CheckString ("test string", checkName = "test2")
[<Fact>]
let ``Example with anonymous record`` () =
    check.CheckJsonObject {| a = 1; b = "tukabel" |}

Non-deterministic strings

If the test output contains some randomly generated UUIDs it isn't possible to test that the output is always the same. To fix the problem, you would either have to use a seeded random generator, or replace the UUIDs after the fact. CheckTestOutput has a helper functionality which allows you to replace random UUIDs with deterministically generated ones.

You can enable it by setting sanitizeGuids: true when creating OutputChecker (or sanitizeQuotedGuids to sanitize GUIDs in quotes):

OutputChecker check = new OutputChecker("testoutputs", sanitizeGuids: true);

[Fact]
public void CheckGuidsJson()
{
    var id1 = Guid.NewGuid();
    var id2 = Guid.NewGuid();

    check.CheckJsonObject(new { id1, id2, id3 = id1 });
}

The sanitization preserves equality - it replaces different UUIDs with different stub string and same UUID with the same string. In this case, the checked JSON will be this:

{
	"id1": "aaaaaaaa-bbbb-cccc-dddd-000000000001",
	"id2": "aaaaaaaa-bbbb-cccc-dddd-000000000002",
	"id3": "aaaaaaaa-bbbb-cccc-dddd-000000000001"
}

While mostly used for UUIDs, we can replace anything that can be found by a regular expression - just specify a list of regular expressions in the nonDeterminismSanitizers parameter.

Custom checks

The CheckString, CheckLines and CheckJsonObject are just extension methods on the OutputChecker class and you can write your own. The only thing to keep in mind is to include and propagate the CallerMemberName and CallerFilePath attributes. As a simple example, this is how to implement a simple helper that changes the default file extension to js:

public static void CheckJavascript(
    this OutputChecker t,
    string output,
    string checkName = null,
    string fileExtension = "js",
    [System.Runtime.CompilerServices.CallerMemberName] string memberName = null,
    [System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = null)
{
    t.CheckString(output, checkName, fileExtension, memberName, sourceFilePath);
}

For more inspiration, have a look at CheckExtensions class in the Coberec project.

Installation

NuGet package ¯\(ツ)/¯.

dotnet add package CheckTestOutput

Alternatively, you can just grab the source codes from src folder and copy them into your project (it's MIT licensed, so just keep a link to this project in the copied code). This library does not have any other dependencies (on new enough dotnet).

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 is compatible.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 is compatible.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 is compatible. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories (1)

Showing the top 1 popular GitHub repositories that depend on CheckTestOutput:

Repository Stars
riganti/dotvvm
Open source MVVM framework for Web Apps
Version Downloads Last updated
0.6.3 1,459 7/30/2023
0.6.2 141 7/30/2023
0.6.1 148 7/25/2023
0.6.0 555 1/28/2023
0.5.1 2,117 6/11/2022
0.4.3 2,620 6/16/2022
0.4.2 9,474 11/7/2021
0.4.1 298 11/7/2021
0.4.0 3,010 7/17/2020
0.3.0 651 12/20/2019
0.2.2 610 3/5/2019
0.2.1 573 3/5/2019
0.2.0 693 3/1/2019
0.1.0 862 1/23/2019