{"id":13735992,"url":"https://github.com/SwissLife-OSS/snapshooter","last_synced_at":"2025-05-08T12:31:47.868Z","repository":{"id":34154871,"uuid":"165853566","full_name":"SwissLife-OSS/snapshooter","owner":"SwissLife-OSS","description":"Snapshooter is a snapshot testing tool for .NET Core and .NET Framework","archived":false,"fork":false,"pushed_at":"2025-02-05T11:39:33.000Z","size":3006,"stargazers_count":315,"open_issues_count":63,"forks_count":30,"subscribers_count":10,"default_branch":"master","last_synced_at":"2025-04-11T23:05:46.152Z","etag":null,"topics":["csharp","json","net","snapshot","snapshot-testing","test","testing","tool","xunit"],"latest_commit_sha":null,"homepage":"https://swisslife-oss.github.io/snapshooter/","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/SwissLife-OSS.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2019-01-15T13:14:32.000Z","updated_at":"2025-03-22T20:21:48.000Z","dependencies_parsed_at":"2024-01-17T09:46:07.490Z","dependency_job_id":"2cf61829-531c-4ae6-b942-48e95100f4c3","html_url":"https://github.com/SwissLife-OSS/snapshooter","commit_stats":{"total_commits":105,"total_committers":19,"mean_commits":5.526315789473684,"dds":0.5809523809523809,"last_synced_commit":"904a2525e246d5878c3ec0104cbfc1461e1e17dd"},"previous_names":[],"tags_count":39,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SwissLife-OSS%2Fsnapshooter","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SwissLife-OSS%2Fsnapshooter/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SwissLife-OSS%2Fsnapshooter/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SwissLife-OSS%2Fsnapshooter/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/SwissLife-OSS","download_url":"https://codeload.github.com/SwissLife-OSS/snapshooter/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253068605,"owners_count":21848838,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["csharp","json","net","snapshot","snapshot-testing","test","testing","tool","xunit"],"created_at":"2024-08-03T03:01:13.909Z","updated_at":"2025-05-08T12:31:45.538Z","avatar_url":"https://github.com/SwissLife-OSS.png","language":"C#","funding_links":[],"categories":["Libraries","Testing","测试","Identifiers"],"sub_categories":["Testing","GUI - other"],"readme":"![Snapshooter](https://raw.github.com/swissLife-oss/snapshooter-docs/master/website/static/img/logotype_snapshooter.png)\n\n## [![Nuget](https://img.shields.io/nuget/v/Snapshooter.svg?style=flat)](https://www.nuget.org/packages/Snapshooter.Xunit) [![GitHub Release](https://img.shields.io/github/release/SwissLife-OSS/Snapshooter.svg?style=flat)](https://github.com/SwissLife-OSS/Snapshooter/releases/latest) [![Build Status](https://dev.azure.com/swisslife-oss/swisslife-oss/_apis/build/status/Snapshooter.Release?branchName=master)](https://dev.azure.com/swisslife-oss/swisslife-oss/_build/latest?definitionId=6\u0026branchName=master) [![Coverage Status](https://sonarcloud.io/api/project_badges/measure?project=SwissLife-OSS_Snapshooter\u0026metric=coverage)](https://sonarcloud.io/dashboard?id=SwissLife-OSS_Snapshooter) [![Quality](https://sonarcloud.io/api/project_badges/measure?project=SwissLife-OSS_Snapshooter\u0026metric=alert_status)](https://sonarcloud.io/dashboard?id=SwissLife-OSS_Snapshooter)\n\n**Snapshooter is a snapshot testing tool for _.NET Core_ and _.NET Framework_**\n\n_Snapshooter_ is a flexible snapshot testing tool to simplify the result validation in your unit tests in .Net. It is based on the idea of [Jest Snapshot Testing](https://jestjs.io/docs/en/snapshot-testing/).\n\nTo get more detailed information about Snapshooter, go to the [Snapshooter Docs](https://swisslife-oss.github.io/snapshooter/docs/introduction)\n\n## Getting Started\n\nTo get started, install the Snapshooter _Xunit_ or _NUnit_ nuget package:\n\n### XUnit\n```bash\ndotnet add package Snapshooter.Xunit\n```\n### NUnit\n```bash\ndotnet add package Snapshooter.NUnit\n```\n### MSTest\n```bash\ndotnet add package Snapshooter.MSTest\n```\n\n[Get Started](https://swisslife-oss.github.io/snapshooter/docs/get-started)\n\n### Assert with Snapshots\n\nTo assert your test results with snapshots in your unit tests, follow the following steps:\n\n#### 1. Add snapshot assert statement\n\nInsert a snapshot assert statement `Snapshot.Match(yourResultObject);` into your unit test.\n\nExample:\n\n```csharp\n/// \u003csummary\u003e\n/// Tests if the new created person is valid.\n/// \u003c/summary\u003e\n[Fact]\npublic void CreatePersonSnapshotTest()\n{\n    // arrange\n    var serviceClient = new ServiceClient();\n\n    // act\n    TestPerson person = serviceClient.CreatePerson(\n        Guid.Parse(\"2292F21C-8501-4771-A070-C79C7C7EF451\"), \"David\", \"Mustermann\");\n\n    // assert\n    Snapshot.Match(person);\n}\n```\n\n#### 2. Run the unit test to create a new Snapshot\n\nThe `Snapshot.Match(person)` statement creates a new snapshot of your result object and stores it in the\n`__snapshots__` folder. The `__snapshots__` folder is always next to your executed unit test file.\n\nSnapshot name: `\u003cUnitTestClassName\u003e.\u003cTestMethodName\u003e.snap`\n\n#### 3. Review new snapshot\n\nReview your new snapshot file `__snapshots__/\u003cUnitTestClassName\u003e.\u003cTestMethodName\u003e.snap`.\n\n#### 4. Run unit test to assert\n\nNow the `Snapshot.Match(person)` statement will create again a snapshot of your test result and compare it against your reviewed snapshot in the `__snapshots__` folder. The `__snapshots__` folder is always next to your executed unit test file.\n\n### Mismatching Snapshot Handling\n\nIf your result object has changed and the existing snapshot is not matching anymore, then the unit test will fail. The unit test error message will point to the exact mismatching position within the snapshot.\n\nIn addition, in the snapshot folder `__snapshots__` a subfolder with name `__mismatch__` will be created. In this folder you can find\nthe actual snapshot which is mismatching with the existing snapshot in the `__snapshots__` folder. Therefore it is possible to compare the two snapshots with a text compare tool.\n\nIf the snapshot in the mismatching folder `__mismatch__` is correct, just move it to the parent `__snapshots__` folder (override the existing one).\n\n[Read More](https://swisslife-oss.github.io/snapshooter/docs/snapshot-mismatch-handling)\n\n## Different Match-Syntax Possibilities\nThe default match syntax for snapshots is:\n```csharp\n    Snapshot.Match(person);\n```\nHowever, we also could use the fluent syntax:\n```csharp\n    person.MatchSnapshot();\n```\nOr we can use FluentAssertion's should() syntax:\n```csharp\n    person.Should().MatchSnapshot();\n```\nFor NUnit we will support the Assert.That syntax (Coming soon):\n```csharp\n    Assert.That(person, Match.Snapshot());\n```\n\n## Features\n\n### Ignore Fields in Snapshots\n\nIf some fields in your snapshot shall be ignored during snapshot assertion, then the following ignore options can be used:\n\n```csharp\n[Fact]\npublic void CreatePersonSnapshot_IgnoreId()\n{\n    // arrange\n    var serviceClient = new ServiceClient();\n\n    // act\n    TestPerson person = serviceClient.CreatePerson(\"Hans\", \"Muster\");\n\n    // assert\n    Snapshot.Match\u003cPerson\u003e(testPerson, matchOptions =\u003e matchOptions.IgnoreField(\"Size\"));\n}\n```\n\nThe fields to ignore will be located via JsonPath, therefore you are very flexible and you can also ignore fields from child objects or arrays.\n\nIgnore Examples:\n\n```csharp\n// Ignores the field 'StreetNumber' of the child node 'Address' of the person\nSnapshot.Match\u003cPerson\u003e(person, matchOptions =\u003e matchOptions.IgnoreField(\"Address.StreetNumber\"));\n\n// Ignores the field 'Name' of the child node 'Country' of the child node 'Address' of the person\nSnapshot.Match\u003cPerson\u003e(person, matchOptions =\u003e matchOptions.IgnoreField(\"Address.Country.Name\"));\n\n// Ignores the field 'Id' of the first person in the 'Relatives' array of the person\nSnapshot.Match\u003cPerson\u003e(person, matchOptions =\u003e matchOptions.IgnoreField(\"Relatives[0].Id\"));\n\n// Ignores the field 'Name' of all 'Children' nodes of the person\nSnapshot.Match\u003cPerson\u003e(person, matchOptions =\u003e matchOptions.IgnoreField(\"Children[*].Name\"));\n\n// Ignores all fields with name 'Id'\nSnapshot.Match\u003cPerson\u003e(person, matchOptions =\u003e matchOptions.IgnoreField(\"**.Id\"));\n```\n\n#### Ignore All Fields by name\nIf we want to ignore all fields by a specific name, then we have two options:\n\nOption 1: Use the ignore match option 'IgnoreAllFields(\u003cfieldName\u003e)' and add the name.\n```csharp\n// Ignores all fields with name 'Id'\nSnapshot.Match\u003cPerson\u003e(person, matchOptions =\u003e matchOptions.IgnoreAllFields(\"Id\"));\n```\n\nOption 2: Use the default ignore match option 'IgnoreFields(**.\u003cfieldName\u003e)' with the following JsonPath syntax **.\u003cfieldName\u003e \n```csharp\n// Ignores all fields with name 'Id'\nSnapshot.Match\u003cPerson\u003e(person, matchOptions =\u003e matchOptions.IgnoreFields(\"**.Id\"));\n```\n\n### Hash Fields in Snapshots\n\nIf some fields of our snapshot are too big, for example a binary field with a lot of data, then we can use the HashField option.\nThe HashField option creates a Hash of the field value and therefore each time only the hash is compared. \nIf there is a change in the field value, then the snapshot match will fail.\n\n```csharp\n[Fact]\npublic void ImageSnapshot_HashImageBinary()\n{\n    // arrange\n    var serviceClient = new ServiceClient();\n\n    // act\n    TestImage image = serviceClient.CreateMonaLisaImage();\n\n    // assert\n    Snapshot.Match(image, matchOptions =\u003e matchOptions.HashField(\"Data\"));\n}\n```\nExample Snapshot with Hash\n```json\n{\n  \"Id\": 3450987,\n  \"OwnerId\": \"0680faef-6e89-4d52-bad8-291053c66696\",\n  \"Name\": \"Mona Lisa\",\n  \"CreationDate\": \"2020-11-10T21:23:09.036+01:00\",\n  \"Price\": 951868484.345,\n  \"Data\": \"m+sQR9KG9WpgYoQiRASPkt9FLJOLsjK86UuiXKVRzas=\"  \n}\n```\n\nThe field(s) to hash can be located via JsonPath or via field name.\n\nHash Field Examples:\n\n```csharp\n// Hash the field 'Data' of the child node 'Thumbnail' of the person\nSnapshot.Match\u003cPerson\u003e(person, matchOptions =\u003e matchOptions.HashField(\"Thumbnail.Data\"));\n\n// Hash the field 'Data' of the first thumbnail in the 'Thumbnails' array of the image\nSnapshot.Match\u003cPerson\u003e(person, matchOptions =\u003e matchOptions.HashField(\"Thumbnails[0].Data\"));\n\n// Ignores the field 'Data' of all 'Thumbnails' nodes of the image\nSnapshot.Match\u003cPerson\u003e(person, matchOptions =\u003e matchOptions.HashField(\"Thumbnails[*].Data\"));\n\n// Ignores all fields with name 'Data'\nSnapshot.Match\u003cPerson\u003e(person, matchOptions =\u003e matchOptions.HashField(\"**.Data\"));\n```\n\n### Assert Fields in Snapshots\n\nSometimes there are fields in a snapshot, which you want to assert separately against another value.\n\nFor Example, the Id field of a 'Person' is always newly generated in a service,\ntherefore you receive in the test always a Person with a new id (Guid).\nNow if you want to check that the id is not an empty Guid, the `Assert` option can be used.\n\n```csharp\n/// \u003csummary\u003e\n/// Tests if the new created person is valid and the person id is not empty.\n/// \u003c/summary\u003e\n[Fact]\npublic void CreatePersonSnapshot_AssertId()\n{\n    // arrange\n    var serviceClient = new ServiceClient();\n\n    // act\n    TestPerson person = serviceClient.CreatePerson(\"Hans\", \"Muster\"); // --\u003e id is created within the service\n\n    // assert\n    Snapshot.Match\u003cPerson\u003e(testPerson, matchOption =\u003e matchOption.Assert(\n                    fieldOption =\u003e Assert.NotEqual(Guid.Empty, fieldOption.Field\u003cGuid\u003e(\"Id\"))));\n}\n```\n\nThe fields to assert will be located via JsonPath, therefore you are very flexible and you can also ignore fields from child objects or arrays.\n\nAssert Examples:\n\n```csharp\n// Assert the field 'Street' of the 'Address' of the person\nSnapshot.Match\u003cPerson\u003e(person, matchOption =\u003e matchOption.Assert(\n                    fieldOption =\u003e Assert.Equal(15, fieldOption.Field\u003cint\u003e(\"Address.StreetNumber\"))));\n\n// Asserts the field 'Code' of the field 'Country' of the 'Address' of the person\nSnapshot.Match\u003cPerson\u003e(person, matchOption =\u003e matchOption.Assert(\n                    fieldOption =\u003e Assert.Equal(\"De\", fieldOption.Field\u003cCountryCode\u003e(\"Address.Country.Code\"))));\n\n// Asserts the fist 'Id' field of the 'Relatives' array of the person\nSnapshot.Match\u003cPerson\u003e(person, \u003e matchOption.Assert(\n                    fieldOption =\u003e Assert.NotNull(fieldOption.Field\u003cstring\u003e(\"Relatives[0].Id\"))));\n\n// Asserts every 'Id' field of all the 'Relatives' of the person\nSnapshot.Match\u003cPerson\u003e(person, \u003e matchOption.Assert(\n                    fieldOption =\u003e Assert.NotNull(fieldOption.Fields\u003cstring\u003e(\"Relatives[*].Id\"))));\n \n// Asserts 'Relatives' array is not empty\nSnapshot.Match\u003cPerson\u003e(person, \u003e matchOption.Assert(\n                    fieldOption =\u003e Assert.NotNull(fieldOption.Fields\u003cTestPerson\u003e(\"Relatives[*]\"))));\n\n```\n\nThe Snapshooter assert functionality is not limited to Xunit or NUnit asserts, it also could be used Fluent Assertions or another assert tool.\n\n### Concatenate Ignore \u0026 Asserts checks\n\nAll the ignore, isType or assert field checks can be concatenated.\n\n```csharp\n[Fact]\npublic void Match_ConcatenateFieldChecksTest_SuccessfulMatch()\n{\n    // arrange\n    var serviceClient = new ServiceClient();\n\n    // act\n    TestPerson person = serviceClient.CreatePerson(\"Hans\", \"Muster\");\n\n    // act \u0026 assert\n    Snapshot.Match\u003cTestPerson\u003e(testPerson, matchOption =\u003e matchOption\n            .Assert(option =\u003e Assert.NotEqual(Guid.Empty, option.Field\u003cGuid\u003e(\"Id\")))\n            .IgnoreField\u003cDateTime\u003e(\"CreationDate\")\n            .Assert(option =\u003e Assert.Equal(-58, option.Field\u003cint\u003e(\"Address.StreetNumber\")))\n            .Assert(option =\u003e testChild.Should().BeEquivalentTo(option.Field\u003cTestChild\u003e(\"Children[3]\")))\n            .IgnoreField\u003cTestCountry\u003e(\"Address.Country\")\n            .Assert(option =\u003e Assert.Null(option.Field\u003cTestCountry\u003e(\"Relatives[0].Address.Plz\"))));\n}\n```\n\n## Using Snapshooter in CI-Builds\n\nWhen running snapshooter tests in a CI-build you might want to ensure that a snapshots are correctly checked-in since otherwise tests without a snapshot will just create the initial snapshot and become green.\n\nIn order to fail tests that are without a snapshot on your CI-build you can set the snapshooter behavior to strict-mode by setting the environment variable `SNAPSHOOTER_STRICT_MODE` to `on` or `true`.\n\n## Community\n\nThis project has adopted the code of conduct defined by the [Contributor Covenant](https://contributor-covenant.org/)\nto clarify expected behavior in our community. For more information, see the [Swiss Life OSS Code of Conduct](https://swisslife-oss.github.io/coc).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FSwissLife-OSS%2Fsnapshooter","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FSwissLife-OSS%2Fsnapshooter","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FSwissLife-OSS%2Fsnapshooter/lists"}