Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
04f248c
Updates the version.
GillesTourreau Sep 24, 2024
95a5302
Refactoring of the comparison.
GillesTourreau Sep 24, 2024
66d3e90
Implements the ToString() method for the object model.
GillesTourreau Sep 24, 2024
95e19bb
Remove some warnings
GillesTourreau Sep 24, 2024
f28d204
Add the SqlObjectPropertyDifference.ToString() implementation.
GillesTourreau Sep 24, 2024
9e63486
Fix namespaces
GillesTourreau Sep 24, 2024
aec24bc
Updates the unit tests to compare the indexes filtered.
GillesTourreau Sep 24, 2024
24dc0e3
Update unit tests to check the type of unique constraint / primary keys.
GillesTourreau Sep 24, 2024
6b0b60c
Remove warning
GillesTourreau Sep 24, 2024
788bd36
Factorize duplicated code.
GillesTourreau Sep 24, 2024
61df3f9
Add textual representation of the comparison results.
GillesTourreau Sep 25, 2024
a99af67
Add asynchronous operation to retrieve schema in parallel.
GillesTourreau Sep 25, 2024
39ad4d7
Update the unit tests for the computed columns.
GillesTourreau Sep 25, 2024
82f500c
Add unit tests for the columns.
GillesTourreau Sep 25, 2024
f544ac2
Remove the SqlDatabase prefix for some classes.
GillesTourreau Sep 25, 2024
194590f
Remove SystemTypeId property in the columns and the user type.
GillesTourreau Sep 25, 2024
d50e609
Fix warnings.
GillesTourreau Sep 25, 2024
ae110f3
Add an option to specify the tables to ignore in the comparison.
GillesTourreau Sep 25, 2024
5036a9e
Fix some warnings.
GillesTourreau Sep 25, 2024
d72a697
Deploy the source and target in parallel.
GillesTourreau Sep 25, 2024
ad15903
Improvement of the SqlObjectDifferences.ToString() to display the obj…
GillesTourreau Sep 25, 2024
99a832a
Fix indentation for the ToString() methods.
GillesTourreau Sep 25, 2024
2c6c9de
Add a property in the comparison results to indicate if the two datab…
GillesTourreau Sep 25, 2024
6154840
Fix bug when comparing the included columns in the indexes.
GillesTourreau Sep 25, 2024
e4d9835
Fix queries to remove the "__EFMigrationsHistory" filter.
GillesTourreau Sep 25, 2024
75398d0
Add the UnitTests.Databases.SqlServer.Dac project.
GillesTourreau Sep 26, 2024
6191708
Add CreateDatabaseAsync() extension method for DbContext.
GillesTourreau Sep 26, 2024
729b7a0
Remove some warnings.
GillesTourreau Sep 26, 2024
42d1df5
Remove not used namespace.
GillesTourreau Sep 26, 2024
1b388b3
Remove creation of the user account when initializing a database.
GillesTourreau Sep 26, 2024
cb5e59c
Add documentation / samples for the test of database migration.
GillesTourreau Sep 26, 2024
1115cdb
Reduce dependencies to EF / SQL Server Client version.
GillesTourreau Sep 26, 2024
e18043d
Fix documentation
GillesTourreau Sep 26, 2024
47a9e94
Fix the readme.
GillesTourreau Sep 26, 2024
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
14 changes: 12 additions & 2 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
[*]
[*.csproj]
indent_style = space
indent_size = 4
indent_size = 2

[*.cs]
indent_style = space
indent_size = 4

# StyleCop

Expand All @@ -14,3 +16,11 @@ dotnet_diagnostic.SA1601.severity = none

# SA1602: Enumeration items should be documented
dotnet_diagnostic.SA1602.severity = none

# Visual Studio

# IDE0130: Namespace does not match folder structure
dotnet_diagnostic.IDE0130.severity = none

# IDE0290: Use primary constructor
dotnet_diagnostic.IDE0290.severity = none
9 changes: 8 additions & 1 deletion .github/workflows/github-actions-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ on:
type: string
description: The version of the library
required: true
default: 1.0.0
default: 2.0.0
VersionSuffix:
type: string
description: The version suffix of the library (for example rc.1)
Expand Down Expand Up @@ -39,5 +39,12 @@ jobs:
--property:VersionSuffix=${{ github.event.inputs.VersionSuffix }}
"src/UnitTests.Databases.SqlServer.EntityFramework/UnitTests.Databases.SqlServer.EntityFramework.csproj"

- name: Build UnitTests.Databases.SqlServer.Dac
run: dotnet pack
--property:Configuration=Release
--property:VersionPrefix=${{ github.event.inputs.VersionPrefix }}
--property:VersionSuffix=${{ github.event.inputs.VersionSuffix }}
"src/UnitTests.Databases.SqlServer.Dac/UnitTests.Databases.SqlServer.Dac.csproj"

- name: Publish the package to nuget.org
run: dotnet nuget push "src/**/bin/Release/*.nupkg" --api-key "${{ secrets.NUGET_APIKEY }}" --source https://api.nuget.org/v3/index.json
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
<RunAnalyzers Condition="'$(Configuration)' == 'Release'">false</RunAnalyzers>

<!-- Disable the StyleCop 'XML comment analysis is disabled due to project configuration' warning. -->
<NoWarn>$(NoWarn);SA0001</NoWarn>
<NoWarn>$(NoWarn);SA0001;NU1903</NoWarn>

<!-- By default prefix all the assemblies name with ChantierConnect -->
<AssemblyName>PosInformatique.$(MSBuildProjectName)</AssemblyName>
Expand Down
13 changes: 7 additions & 6 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="coverlet.collector" Version="6.0.2" />
<PackageVersion Include="FluentAssertions" Version="6.12.0" />
<PackageVersion Include="Microsoft.EntityFrameworkCore" Version="8.0.8" />
<PackageVersion Include="FluentAssertions" Version="6.12.1" />
<PackageVersion Include="Microsoft.Data.SqlClient" Version="5.0.1" />
<PackageVersion Include="Microsoft.EntityFrameworkCore" Version="8.0.0" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.0" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.11.0" />
<PackageVersion Include="Microsoft.SqlServer.DacFx" Version="162.3.566" />
<PackageVersion Include="PosInformatique.UnitTests.Databases.SqlServer.EntityFramework" Version="0.9.11" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.0" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
<PackageVersion Include="Microsoft.SqlServer.DacFx" Version="161.6374.0" />
<PackageVersion Include="StyleCop.Analyzers" Version="1.2.0-beta.556" />
<PackageVersion Include="xunit" Version="2.9.0" />
<PackageVersion Include="xunit" Version="2.9.1" />
<PackageVersion Include="xunit.runner.visualstudio" Version="2.8.2" />
</ItemGroup>
</Project>
7 changes: 7 additions & 0 deletions PosInformatique.UnitTests.Databases.sln
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,11 @@ EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{8500A9B6-CAA0-432C-BABB-DDC86CE08994}"
ProjectSection(SolutionItems) = preProject
docs\WriteUnitTests.md = docs\WriteUnitTests.md
docs\WriteDatabaseMigrationUnitTest.md = docs\WriteDatabaseMigrationUnitTest.md
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnitTests.Databases.SqlServer.Dac", "src\UnitTests.Databases.SqlServer.Dac\UnitTests.Databases.SqlServer.Dac.csproj", "{8BE60460-EBA5-43DE-B85D-C756E2988DC8}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -94,6 +97,10 @@ Global
{04A7AE8F-FE77-435B-9250-600388BB8065}.Debug|Any CPU.Build.0 = Debug|Any CPU
{04A7AE8F-FE77-435B-9250-600388BB8065}.Release|Any CPU.ActiveCfg = Release|Any CPU
{04A7AE8F-FE77-435B-9250-600388BB8065}.Release|Any CPU.Build.0 = Release|Any CPU
{8BE60460-EBA5-43DE-B85D-C756E2988DC8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8BE60460-EBA5-43DE-B85D-C756E2988DC8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8BE60460-EBA5-43DE-B85D-C756E2988DC8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8BE60460-EBA5-43DE-B85D-C756E2988DC8}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
76 changes: 65 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,30 +4,39 @@
It simplifies writing and executing tests, helping ensure your database and data access code are reliable and bug-free.
It is ideal for developers who want to validate data access based on SQL Server code during their development.

This set of tools supports unit testing of the data access layer based on SQL Server.
This set of tools supports unit testing of the persistence layer based on SQL Server.
Any kind of data access framework can be used with these tools:
- Raw ADO .NET queries.
- Entity Framework.
- Dapper.
- ...

You can also use this tools to create and run integration tests with the
[Integration tests in ASP.NET Core](https://learn.microsoft.com/en-us/aspnet/core/test/integration-tests?view=aspnetcore-8.0)
approach.

Since the version 2.0.0 this tools provide a comparer to compare the schema of two SQL databases.

## The approach of these tools

The main approach of these tools is to perform unit tests without using mocking or in-memory alternatives for ADO .NET code or Entity Framework `DbContext`, instead using a real SQL Server database.

Also, these tools offer simple way to compare two SQL databases to test migration script (or Entity Framework migration) when upgrading a database.

### Why is this approach recommended?

- Around 30% to 40% of the code in applications is located in the Data Access layer or repository components. Because it is hard to unit test, developers often skip testing,
- Around 30% to 40% of the code in applications is located in the persistence layer or repository components. Because it is hard to unit test, developers often skip testing,
resulting in lower code coverage.
- When using a mock or in-memory approach for a `DbContext`, you don't truly test the Entity Framework mapping to your database, especially additional SQL constraints like nullability, uniqueness, foreign key cascades, etc.
You also miss technical behaviors like transactions, connection management, triggers, etc.
- When inserting data, it is crucial to ensure that the data in the columns are stored correctly (null/not null values, enum values to numerical values, custom or JSON serialized data, etc.).
- If you use Entity Framework, you can detect warnings/errors raised by the `DbContext` during the development.
- You perform unit test cases, meaning you write simple tests to validate small features instead of writing complex integration tests.
- When changing the schema of the database, it is important to test and have a *safeguard* to check that the migration script (or Entity Framework migration actions) will update the database to the expected schema.

## How to unit test a Data Access Layer
## How to unit test a persistence layer

To perform unit tests of a Data Access Layer, the approach is straightforward using the Arrange/Act/Assert pattern:
To perform unit tests of a persistence layer, the approach is straightforward using the Arrange/Act/Assert pattern:

Before each unit test (`TestMethod` or `Fact` methods):

Expand All @@ -48,6 +57,34 @@ Before each unit test (`TestMethod` or `Fact` methods):

To write a unit test using this approach with the [PosInformatique.UnitTests.Databases](https://github.com/PosInformatique/PosInformatique.UnitTests.Databases) tools, see the [Write unit tests to test the Data Access Layer](./docs/WriteUnitTests.md) page.

## How to unit test database migration

To perform unit tests of a database migration, the approach is straightforward and required only a unit test which perform the following actions:

1. Create an empty database (*initial database*).

2. Create a secondary database with the targeted schema (*target database*).

There are two ways to do this:
- Deploy a DACPAC file (built by a SQL Server Database project).
- Or create a database from a `DbContext` using Entity Framework.

3. Execute your database *migration code* on the *initial database*.

Your database *migration code* can be:
- A simple SQL script file.
- An Entity Framework migration sets executed with the `MigrateAsync()` method.

4. Compare the two databases schemas (*initial* and *target*).

If the database *migration code* works, the *initial* and *target* must have the same schema.

> **NB**: The initial database is not necessarily empty. It can be at a specific schema version X if we want to test the migration from version X to Y.

This approach does not test the migration of the data within the database. We can modify this process to inject some data in the first step to test it,
but writing the unit test can be time-consuming. By focusing on the schema migration of the database, you can verify at least 80-90% of your database
migration code. It's better than nothing and very useful for detecting issues during development or in a CI process!

## What do the PosInformatique.UnitTests.Databases tools provide?

Using the previous approach, the [PosInformatique.UnitTests.Databases](https://github.com/PosInformatique/PosInformatique.UnitTests.Databases) libraries allow you to:
Expand All @@ -59,33 +96,36 @@ Using the previous approach, the [PosInformatique.UnitTests.Databases](https://g

- Offer helpers to easily query and retrieve data from SQL tables (for assertions).

- Contain a comparer tool to check schema differences between two databases.

## NuGet packages

The [PosInformatique.UnitTests.Databases](https://github.com/PosInformatique/PosInformatique.UnitTests.Databases) tools are provided in two NuGet packages:

- [PosInformatique.UnitTests.Databases.SqlServer](https://www.nuget.org/packages/PosInformatique.UnitTests.Databases.SqlServer) NuGet package which contains:
- Tools to deploy a SQL Server database using a DACPAC file before each unit test.
- Helpers to initialize SQL Server databases with sample data.
- Helpers to easily query SQL Server databases.
- Helpers to compare the schema of two SQL Server databases.

- [PosInformatique.UnitTests.Databases.SqlServer.Dac](https://www.nuget.org/packages/PosInformatique.UnitTests.Databases.SqlServer.Dac) NuGet package which contains:
- Tools to deploy a SQL Server database using a DACPAC file before each unit test.

- [PosInformatique.UnitTests.Databases.SqlServer.EntityFramework](https://www.nuget.org/packages/PosInformatique.UnitTests.Databases.SqlServer.EntityFramework) NuGet package which contains:
- Tools to deploy a SQL Server database using a DbContext.

This package uses and includes the previous [PosInformatique.UnitTests.Databases.SqlServer](https://www.nuget.org/packages/PosInformatique.UnitTests.Databases.SqlServer) NuGet package.

## Samples / Demo

A complete sample solution is available in this repository inside the [samples](./samples) folder.

The solution contains the following sample projects:
- [DemoApp.Domain](./samples/DemoApp.Domain/DemoApp.Domain.csproj): Represents the domain of the application with a set of sample business entities.
- [DemoApp.DataAccessLayer](./samples/DemoApp.DataAccessLayer/DemoApp.DataAccessLayer.csproj): Represents a Data Access Layer with a set of repositories to unit test.
- [DemoApp.DataAccessLayer](./samples/DemoApp.DataAccessLayer/DemoApp.DataAccessLayer.csproj): Represents a persistence layer with a set of repositories to unit test.
- [DemoApp.DataAccessLayer.Tests](./samples/DemoApp.DataAccessLayer.Tests/DemoApp.DataAccessLayer.Tests.csproj): Unit test project to test the [DemoApp.DataAccessLayer](./samples/DemoApp.DataAccessLayer/DemoApp.DataAccessLayer.csproj)
project using the [PosInformatique.UnitTests.Databases.SqlServer.EntityFramework](https://www.nuget.org/packages/PosInformatique.UnitTests.Databases.SqlServer.EntityFramework) package.

## Writing unit tests for a Data Access Layer
## Writing unit tests for a persistence layer

To write unit tests for a Data Access Layer, follow the [Write unit tests to test the Data Access Layer](./docs/WriteUnitTests.md) documentation page, which explains the different steps to perform
To write unit tests for a persistence layer, follow the [Write unit tests to test the persistence layer](./docs/WriteUnitTests.md) documentation page, which explains the different steps to perform
using the [PosInformatique.UnitTests.Databases.SqlServer.EntityFramework](https://www.nuget.org/packages/PosInformatique.UnitTests.Databases.SqlServer.EntityFramework) library:

- [Create the SQL Server instance](./docs/WriteUnitTests.md#create-the-sql-server-instance)
Expand All @@ -99,4 +139,18 @@ using the [PosInformatique.UnitTests.Databases.SqlServer.EntityFramework](https:
- [Write the unit tests for methods that retrieve data](./docs/WriteUnitTests.md#write-the-unit-tests-for-methods-that-retrieve-data)
- [Write the unit tests for methods that update the data](./docs/WriteUnitTests.md#write-the-unit-tests-for-methods-that-update-the-data)
- [Execute the unit tests](./docs/WriteUnitTests.md#execute-the-unit-tests)
- [Check the database state after a unit test has failed](./docs/WriteUnitTests.md#check-the-database-state-after-an-unit-test-has-been-failed)
- [Check the database state after a unit test has failed](./docs/WriteUnitTests.md#check-the-database-state-after-an-unit-test-has-been-failed)

## Writing unit test to check database migration

To write an unit test to check the migration of database, follow the [Write unit tests to test database migration](./docs/WriteDatabaseMigrationUnitTest.md)
documentation page.

For Entity Framework migration:
- [Create the SQL Server instance](./docs/WriteUnitTests.md#create-the-sql-server-instance)
- [Create the LocalDB instance](./docs/WriteUnitTests.md#create-the-localdb-instance)
- [Entity Framework migration approach](./docs/WriteDatabaseMigrationUnitTest.md#entity-framework-migration-approach)
- [Create the unit tests project](./docs/WriteDatabaseMigrationUnitTest.md#create-the-unit-tests-project)
- [Add the NuGet packages](./docs/WriteDatabaseMigrationUnitTest.md#add-the-nuget-packages)
- [Write unit test to check the migration of the database](./docs/WriteDatabaseMigrationUnitTest.md#write-unit-test-to-check-the-migration-of-the-database)
- [Check the report details of the `SqlServerDatabaseComparer` tool](./docs/WriteDatabaseMigrationUnitTest.md#check-the-report-details-of-the-sqlserverdatabasecomparer-tool)
Loading