Skip to content

Commit

Permalink
Updated Snapper to create snapshot if none exists (#71)
Browse files Browse the repository at this point in the history
* Updated Snapper to create snapshot if none exists

* Update test projects to .net core 3.1

* Bump appveyor image to VS 2019

* Fix bug with updating snapshots in CI env

* Update docs to mention auto creating of initial snapshot

* Update test projects to use .net 452 so that the latest xunit works

* Remove appveyor logging as its not possible to make it work perfectly

For NUnit tests to be logged in appveyor you need to the use the AppVeyor logger. 
Unfortunately since we run the same test twice, once for .net core and once for .net framework the AppVeyor logger doesn't detect the difference and only logs the test once, which results in incorrect numbers. 

Removed AppVeyor  logger fully and not going to rely on the number in CI.
  • Loading branch information
theramis committed Jan 11, 2021
1 parent f8b66ec commit 33ae663
Show file tree
Hide file tree
Showing 15 changed files with 209 additions and 91 deletions.
4 changes: 1 addition & 3 deletions appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ skip_branch_with_pr: true
max_jobs: 1
configuration: Release

image: Visual Studio 2017
image: Visual Studio 2019

init:
- git config --global core.autocrlf input
Expand All @@ -29,8 +29,6 @@ test: off

test_script:
- ps: dotnet test --no-build
- ps: dotnet test Tests/Snapper.Nunit.Tests/Snapper.Nunit.Tests.csproj --logger:Appveyor --no-build
- ps: dotnet test Tests/Snapper.TestFrameworkSupport.Tests/Snapper.TestFrameworkSupport.Tests.csproj --logger:Appveyor --no-build

deploy:
- provider: NuGet
Expand Down
56 changes: 29 additions & 27 deletions docs/pages/quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,13 @@ To run this quickstart, you need the following prerequisites:
```bash
nuget install Snapper
```
2. Create an xUnit test like shown
1. Create an xUnit test like shown
```csharp
public class MyTestClass {

[Fact]
public void MyTest(){
public void MyTest()
{
var obj = new {
Key = "value"
};
Expand All @@ -28,42 +29,26 @@ public class MyTestClass {
}
}
```
Run the test and you'll see that it passes.
The above code will try match the `obj` variable with a snapshot in the file `_snapshots/MyTestClass_MyTest.json` (relative to the file in which `MyTestClass` exists).
For a new test this will fail as the file does not exist with an error similiar to this.
```
Snapper.Exceptions.SnapshotDoesNotExistException : A snapshot does not exist.
Apply the [UpdateSnapshots] attribute on the test method or class and then run the test again to create a snapshot.
```
3. Lets create the snapshot so that the test passes. We could create the snapshot file manually if we wanted but that's a bit annoying. Luckily Snapper can generate a snapshot file for us! Apply the `[UpdateSnapshots]` attribute on the method and run the test again.
```csharp
public class MyTestClass {

[UpdateSnapshots]
[Fact]
public void MyTest(){
var obj = new {
Key = "value"
};

obj.ShouldMatchSnapshot();
}
}
```
For a new test the snapshot file would not exist yet so Snapper automatically creates it on the first run.
> Snapper automatically creates a new snapshot file for a new test from version v2.3.0 onwards. For previous versions use the `[UpdateSnapshots]` attribute to generate your initial snapshot.
4. A file called `_snapshots/MyTestClass_MyTest.json` should have been created with the following content.
1. A file called `_snapshots/MyTestClass_MyTest.json` should have been created with the following content.
```json
{
"Key": "value"
}
```
You can now remove the `[UpdateSnapshots]` attribute on the method. You should also commit the `_snapshots/MyTestClass_MyTest.json` snapshot file with your source code.
You should commit the `_snapshots/MyTestClass_MyTest.json` snapshot file with your source code.

5. Lets make our test fail. Update your code to the following.
1. Lets make our test fail due to a change in requirements. Update your code to the following.
```csharp
public class MyTestClass {

[Fact]
public void MyTest(){
public void MyTest()
{
var obj = new {
Key = "My new value"
};
Expand All @@ -84,6 +69,23 @@ Run the test and you will see a nice error message showing the difference betwee
+ "Key": "My new value"
}
```
You can then update the snapshot by adding the `[UpdateSnapshots]` attribute to the test and running it again or if the change was invalid fix the failing test.

1. Once you've verified the new snapshot is expected you can update the snapshot by appling the `[UpdateSnapshots]` attribute on the method and then running the test again.
```csharp
public class MyTestClass {

[UpdateSnapshots]
[Fact]
public void MyTest()
{
var obj = new {
Key = "My new value"
};

obj.ShouldMatchSnapshot();
}
}
```
This will update the snapshot file with the latest snapshot. Remember to remove the `[UpdateSnapshots]` attribute before you commit your code!
<br></br>
For more examples of tests written using Snapper see [here](https://github.com/theramis/Snapper/tree/master/project/Tests/Snapper.Tests).
4 changes: 2 additions & 2 deletions project/Snapper/Attributes/UpdateSnapshotsAttribute.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
using System;
using System;

namespace Snapper.Attributes
{
/// <inheritdoc />
/// <summary>
/// Tells Snapper to update snapshots. Can be placed on methods, classes and assembly.
/// Note: Has no effect when on inline snapshots
/// Note: Has no effect when using inline snapshots
/// </summary>
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Assembly)]
public class UpdateSnapshotsAttribute : Attribute
Expand Down
36 changes: 36 additions & 0 deletions project/Snapper/Core/CIEnvironmentDetector.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System;
using System.Collections.Generic;
using System.Linq;

namespace Snapper.Core
{
internal static class CiEnvironmentDetector
{
/// <summary>
/// Based on https://github.com/watson/ci-info/blob/2012259979fc38517f8e3fc74daff714251b554d/index.js#L52-L59
/// </summary>
private static IEnumerable<string> CIEnvironmentVariables = new List<string>
{
"CI", // Travis CI, CircleCI, Cirrus CI, Gitlab CI, Appveyor, CodeShip, dsari
"CONTINUOUS_INTEGRATION", // Travis CI, Cirrus CI
"BUILD_NUMBER", // Jenkins, TeamCity
"BUILD_BUILDNUMBER", // Azure DevOps
"RUN_ID" // TaskCluster, dsari
};

public static bool IsCiEnv()
{
foreach (var envVarTarget in new[] { EnvironmentVariableTarget.Process, EnvironmentVariableTarget.Machine, EnvironmentVariableTarget.User })
{
var found = CIEnvironmentVariables.Any(ciEnvironmentVariable =>
Environment.GetEnvironmentVariable(ciEnvironmentVariable, envVarTarget) != null);
if (found)
{
return true;
}
}

return false;
}
}
}
8 changes: 2 additions & 6 deletions project/Snapper/Core/Messages.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using System;
using System;
using System.Text;
using Snapper.Attributes;
using Snapper.Json;

namespace Snapper.Core
Expand All @@ -12,12 +11,9 @@ public static string GetSnapResultMessage(SnapResult result)
switch (result.Status)
{
case SnapResultStatus.SnapshotDoesNotExist:
var attributeName = nameof(UpdateSnapshotsAttribute).Replace("Attribute", string.Empty);
var message = new StringBuilder();
message.AppendLine($"A snapshot does not exist.{Environment.NewLine}");
message.AppendLine($"Apply the [{attributeName}] attribute on the " +
$"test method or class and then run the test again to " +
$"create a snapshot.");
message.AppendLine("Run the test outside of a CI environment to create a snapshot.");
return message.ToString();
case SnapResultStatus.SnapshotsMatch:
return "Snapshots Match.";
Expand Down
26 changes: 21 additions & 5 deletions project/Snapper/Core/SnapperCore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,8 @@ internal class SnapperCore
protected SnapResult Snap(SnapshotId snapshotId, object newSnapshot)
{
var currentSnapshot = _snapshotStore.GetSnapshot(snapshotId);
var areSnapshotsEqual = currentSnapshot != null
&& _snapshotComparer.CompareSnapshots(currentSnapshot, newSnapshot);

if (!areSnapshotsEqual && _snapshotUpdateDecider.ShouldUpdateSnapshot())

if (ShouldUpdateSnapshot(currentSnapshot, newSnapshot))
{
_snapshotStore.StoreSnapshot(snapshotId, newSnapshot);
return SnapResult.SnapshotUpdated(currentSnapshot, newSnapshot);
Expand All @@ -31,9 +29,27 @@ protected SnapResult Snap(SnapshotId snapshotId, object newSnapshot)
return SnapResult.SnapshotDoesNotExist(newSnapshot);
}

return areSnapshotsEqual
return _snapshotComparer.CompareSnapshots(currentSnapshot, newSnapshot)
? SnapResult.SnapshotsMatch(currentSnapshot, newSnapshot)
: SnapResult.SnapshotsDoNotMatch(currentSnapshot, newSnapshot);
}

private bool ShouldUpdateSnapshot(object currentSnapshot, object newSnapshot)
{
var snapshotsAreEqual = currentSnapshot != null
&& _snapshotComparer.CompareSnapshots(currentSnapshot, newSnapshot);
if (!snapshotsAreEqual && _snapshotUpdateDecider.ShouldUpdateSnapshot())
{
return true;
}

// Create snapshot if it doesn't currently exist and its not a CI env
if (currentSnapshot == null)
{
return !CiEnvironmentDetector.IsCiEnv();
}

return false;
}
}
}
29 changes: 1 addition & 28 deletions project/Snapper/Core/SnapshotUpdateDecider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,6 @@ internal class SnapshotUpdateDecider : ISnapshotUpdateDecider
private const string UpdateSnapshotEnvironmentVariableName = "UpdateSnapshots";
private readonly string _envVarName;

/// <summary>
/// Based on https://github.com/watson/ci-info/blob/2012259979fc38517f8e3fc74daff714251b554d/index.js#L52-L59
/// </summary>
private readonly IEnumerable<string> _ciEnvironmentVariables = new List<string>
{
"CI", // Travis CI, CircleCI, Cirrus CI, Gitlab CI, Appveyor, CodeShip, dsari
"CONTINUOUS_INTEGRATION", // Travis CI, Cirrus CI
"BUILD_NUMBER", // Jenkins, TeamCity
"BUILD_BUILDNUMBER", // Azure DevOps
"RUN_ID" // TaskCluster, dsari
};

public SnapshotUpdateDecider(ITestMethodResolver testMethodResolver,
string envVarName = UpdateSnapshotEnvironmentVariableName)
{
Expand Down Expand Up @@ -59,22 +47,7 @@ private bool ShouldUpdateSnapshotBasedOnAttribute()
{
if (TryGetUpdateSnapshotsAttribute(customAttributeProvider, out var att))
{
return !(att.IgnoreIfCi && IsCiEnv());
}
}

return false;
}

private bool IsCiEnv()
{
foreach (var envVarTarget in new[] { EnvironmentVariableTarget.Process, EnvironmentVariableTarget.Machine, EnvironmentVariableTarget.User})
{
var found = _ciEnvironmentVariables.Any(ciEnvironmentVariable =>
Environment.GetEnvironmentVariable(ciEnvironmentVariable, envVarTarget) != null);
if (found)
{
return true;
return !(att.IgnoreIfCi && CiEnvironmentDetector.IsCiEnv());
}
}

Expand Down
24 changes: 22 additions & 2 deletions project/Tests/Snapper.Internals.Tests/Core/SnapperCoreTests.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using FluentAssertions;
using System;
using FluentAssertions;
using Moq;
using Snapper.Core;
using Xunit;
Expand Down Expand Up @@ -117,8 +118,26 @@ public void SnapshotDoesNotExist_ShouldUpdate_ResultStatusIs_SnapshotUpdated()
}

[Fact]
public void SnapshotDoesNotExist_ResultStatusIs_SnapshotDoesNotExist()
public void SnapshotDoesNotExist_ResultStatusIs_SnapshotUpdated()
{
// Tests run on CI so clearing the CI environment variable to emulate local machine
Environment.SetEnvironmentVariable("CI", null, EnvironmentVariableTarget.Process);

_store.Setup(a => a.GetSnapshot(It.IsAny<SnapshotId>())).Returns(null);
_updateDecider.Setup(a => a.ShouldUpdateSnapshot()).Returns(false);

var result = _snapper.Snap(new SnapshotId("name", null, null, null), _obj);

_store.Verify(a => a.StoreSnapshot(It.IsAny<SnapshotId>(), It.IsAny<object>()), Times.Once);
result.Status.Should().BeEquivalentTo(SnapResultStatus.SnapshotUpdated);
result.OldSnapshot.Should().BeNull();
result.NewSnapshot.Should().BeEquivalentTo(_obj);
}

[Fact]
public void SnapshotDoesNotExist_And_IsCiEnv_ResultStatusIs_SnapshotDoesNotExist()
{
Environment.SetEnvironmentVariable("CI", "true", EnvironmentVariableTarget.Process);
_store.Setup(a => a.GetSnapshot(It.IsAny<SnapshotId>())).Returns(null);
_updateDecider.Setup(a => a.ShouldUpdateSnapshot()).Returns(false);

Expand All @@ -127,6 +146,7 @@ public void SnapshotDoesNotExist_ResultStatusIs_SnapshotDoesNotExist()
result.Status.Should().BeEquivalentTo(SnapResultStatus.SnapshotDoesNotExist);
result.OldSnapshot.Should().BeNull();
result.NewSnapshot.Should().BeEquivalentTo(_obj);
Environment.SetEnvironmentVariable("CI", null, EnvironmentVariableTarget.Process);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ public void TrueEnvironmentVariableSet_ShouldUpdate(string value)
{
Environment.SetEnvironmentVariable(_envVar, value, EnvironmentVariableTarget.Process);
_decider.ShouldUpdateSnapshot().Should().BeTrue();
Environment.SetEnvironmentVariable(_envVar, null, EnvironmentVariableTarget.Process);
}

[Theory]
Expand All @@ -44,6 +45,7 @@ public void FalseEnvironmentVariableSet_ShouldNotUpdate(string value)
{
Environment.SetEnvironmentVariable(_envVar, value, EnvironmentVariableTarget.Process);
_decider.ShouldUpdateSnapshot().Should().BeFalse();
Environment.SetEnvironmentVariable(_envVar, null, EnvironmentVariableTarget.Process);
}

[Theory]
Expand All @@ -54,6 +56,7 @@ public void InvalidEnvironmentVariableSet_ShouldNotUpdate(string value)
{
Environment.SetEnvironmentVariable(_envVar, value, EnvironmentVariableTarget.Process);
_decider.ShouldUpdateSnapshot().Should().BeFalse();
Environment.SetEnvironmentVariable(_envVar, null, EnvironmentVariableTarget.Process);
}

[Fact]
Expand All @@ -69,6 +72,7 @@ public void UpdateAttribute_TrueEnvironmentVariableSet_ShouldUpdate()
{
Environment.SetEnvironmentVariable(_envVar, "true", EnvironmentVariableTarget.Process);
_decider.ShouldUpdateSnapshot().Should().BeTrue();
Environment.SetEnvironmentVariable(_envVar, null, EnvironmentVariableTarget.Process);
}

[Fact]
Expand All @@ -77,6 +81,7 @@ public void UpdateAttribute_FalseEnvironmentVariableSet_ShouldUpdate()
{
Environment.SetEnvironmentVariable(_envVar, "False", EnvironmentVariableTarget.Process);
_decider.ShouldUpdateSnapshot().Should().BeTrue();
Environment.SetEnvironmentVariable(_envVar, null, EnvironmentVariableTarget.Process);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netcoreapp2.2;net45</TargetFrameworks>
<TargetFrameworks>netcoreapp3.1;net452</TargetFrameworks>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
<LangVersion>latest</LangVersion>
Expand Down
5 changes: 2 additions & 3 deletions project/Tests/Snapper.Nunit.Tests/Snapper.Nunit.Tests.csproj
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>netcoreapp2.2;net45</TargetFrameworks>
<TargetFrameworks>netcoreapp3.1;net452</TargetFrameworks>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.3.0" />
<PackageReference Include="NUnit" Version="3.12.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.12.0" />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netcoreapp2.2;net45</TargetFrameworks>
<TargetFrameworks>netcoreapp3.1;net452</TargetFrameworks>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
<LangVersion>latest</LangVersion>
Expand All @@ -13,7 +13,6 @@
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
<PackageReference Include="NUnit" Version="3.12.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.15.1" />
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
<DotNetCliToolReference Include="dotnet-xunit" Version="2.3.1" />
</ItemGroup>
<ItemGroup>
Expand Down

0 comments on commit 33ae663

Please sign in to comment.