Skip to content

Commit 3a2b63f

Browse files
committed
Testing: more flexibility for cleanup
1 parent 8dc0ad4 commit 3a2b63f

File tree

36 files changed

+1433
-1269
lines changed

36 files changed

+1433
-1269
lines changed

Directory.Build.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
<PropertyGroup>
44
<Copyright>(c) $([System.DateTime]::Now.Year), Pawel Gerr. All rights reserved.</Copyright>
5-
<VersionPrefix>4.2.3</VersionPrefix>
5+
<VersionPrefix>4.3.0</VersionPrefix>
66
<Authors>Pawel Gerr</Authors>
77
<GenerateDocumentationFile>true</GenerateDocumentationFile>
88
<PackageProjectUrl>https://dev.azure.com/pawelgerr/Thinktecture.EntityFrameworkCore</PackageProjectUrl>
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
using Microsoft.Data.SqlClient;
2+
using Microsoft.EntityFrameworkCore.Infrastructure;
3+
using Microsoft.EntityFrameworkCore.Migrations;
4+
using Microsoft.EntityFrameworkCore.Storage;
5+
6+
namespace Thinktecture.EntityFrameworkCore.Testing;
7+
8+
/// <summary>
9+
/// Options for test isolation behavior.
10+
/// </summary>
11+
public interface ITestIsolationOptions
12+
{
13+
/// <summary>
14+
/// Test isolation via ambient transaction.
15+
/// </summary>
16+
public static readonly ITestIsolationOptions SharedTablesAmbientTransaction = new ShareTablesIsolation();
17+
18+
/// <summary>
19+
/// Rollbacks migrations and then deletes database objects (like tables).
20+
/// </summary>
21+
public static readonly ITestIsolationOptions RollbackMigrationsAndCleanup = new RollbackMigrationsAndCleanupDatabase();
22+
23+
/// <summary>
24+
/// Deletes database objects (like tables).
25+
/// </summary>
26+
public static readonly ITestIsolationOptions CleanupOnly = new CleanupDatabase();
27+
28+
/// <summary>
29+
/// Indicator, whether the database needs cleanup.
30+
/// </summary>
31+
bool NeedsCleanup { get; }
32+
33+
/// <summary>
34+
/// Cleanup of the database.
35+
/// </summary>
36+
void Cleanup(DbContext dbContext, string schema);
37+
38+
private class ShareTablesIsolation : ITestIsolationOptions
39+
{
40+
public bool NeedsCleanup => false;
41+
42+
public void Cleanup(DbContext dbContext, string schema)
43+
{
44+
}
45+
}
46+
47+
private class RollbackMigrationsAndCleanupDatabase : ITestIsolationOptions
48+
{
49+
public bool NeedsCleanup => true;
50+
51+
public void Cleanup(DbContext dbContext, string schema)
52+
{
53+
RollbackMigrations(dbContext);
54+
DeleteDatabaseObjects(dbContext, schema);
55+
}
56+
}
57+
58+
private class CleanupDatabase : ITestIsolationOptions
59+
{
60+
public bool NeedsCleanup => true;
61+
62+
public void Cleanup(DbContext dbContext, string schema)
63+
{
64+
DeleteDatabaseObjects(dbContext, schema);
65+
}
66+
}
67+
68+
/// <summary>
69+
/// Rollbacks all migrations.
70+
/// </summary>
71+
protected static void RollbackMigrations(DbContext dbContext)
72+
{
73+
ArgumentNullException.ThrowIfNull(dbContext);
74+
75+
dbContext.GetService<IMigrator>().Migrate("0");
76+
}
77+
78+
/// <summary>
79+
/// Deletes database objects (like tables) with provided <paramref name="schema"/>.
80+
/// </summary>
81+
protected static void DeleteDatabaseObjects(DbContext ctx, string schema)
82+
{
83+
ArgumentNullException.ThrowIfNull(ctx);
84+
ArgumentNullException.ThrowIfNull(schema);
85+
86+
var sqlHelper = ctx.GetService<ISqlGenerationHelper>();
87+
88+
ctx.Database.ExecuteSqlRaw(GetSqlForCleanup(), new SqlParameter("@schema", schema));
89+
ctx.Database.ExecuteSqlRaw(GetDropSchemaSql(sqlHelper, schema));
90+
}
91+
92+
private static string GetSqlForCleanup()
93+
{
94+
return @"
95+
DECLARE @crlf NVARCHAR(MAX) = CHAR(13) + CHAR(10);
96+
DECLARE @sql NVARCHAR(MAX);
97+
DECLARE @cursor CURSOR
98+
99+
-- Drop Constraints
100+
SET @cursor = CURSOR FAST_FORWARD FOR
101+
SELECT DISTINCT sql = 'ALTER TABLE ' + QUOTENAME(tc.TABLE_SCHEMA) + '.' + QUOTENAME(tc.TABLE_NAME) + ' DROP ' + QUOTENAME(rc.CONSTRAINT_NAME) + ';' + @crlf
102+
FROM
103+
INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS rc
104+
LEFT JOIN
105+
INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc
106+
ON tc.CONSTRAINT_NAME = rc.CONSTRAINT_NAME
107+
WHERE
108+
tc.TABLE_SCHEMA = @schema
109+
110+
OPEN @cursor FETCH NEXT FROM @cursor INTO @sql
111+
112+
WHILE (@@FETCH_STATUS = 0)
113+
BEGIN
114+
Exec sp_executesql @sql
115+
FETCH NEXT FROM @cursor INTO @sql
116+
END
117+
118+
CLOSE @cursor
119+
DEALLOCATE @cursor
120+
121+
-- Drop Views
122+
SELECT @sql = N'';
123+
SELECT @sql = @sql + 'DROP VIEW ' + QUOTENAME(SCHEMA_NAME(schema_id)) + '.' + QUOTENAME(name) +';' + @crlf
124+
FROM
125+
SYS.VIEWS
126+
WHERE
127+
schema_id = SCHEMA_ID(@schema)
128+
129+
EXEC(@sql);
130+
131+
-- Drop Functions
132+
SELECT @sql = N'';
133+
SELECT @sql = @sql + N' DROP FUNCTION ' + QUOTENAME(SCHEMA_NAME(schema_id)) + N'.' + QUOTENAME(name)
134+
FROM sys.objects
135+
WHERE type_desc LIKE '%FUNCTION%'
136+
AND schema_id = SCHEMA_ID(@schema);
137+
138+
EXEC(@sql);
139+
140+
-- Disable temporal tables
141+
SELECT @sql = N'';
142+
SELECT @sql = @sql + 'IF OBJECTPROPERTY(OBJECT_ID(''' + QUOTENAME(SCHEMA_NAME(schema_id)) + '.' + QUOTENAME(name) +'''), ''TableTemporalType'') = 2' + @crlf
143+
+ ' ALTER TABLE ' + QUOTENAME(SCHEMA_NAME(schema_id)) + '.' + QUOTENAME(name) +' SET (SYSTEM_VERSIONING = OFF);' + @crlf
144+
FROM
145+
SYS.TABLES
146+
WHERE
147+
schema_id = SCHEMA_ID(@schema)
148+
149+
EXEC(@sql);
150+
151+
-- Drop tables
152+
SELECT @sql = N'';
153+
SELECT @sql = @sql + 'DROP TABLE ' + QUOTENAME(SCHEMA_NAME(schema_id)) + '.' + QUOTENAME(name) +';' + @crlf
154+
FROM
155+
SYS.TABLES
156+
WHERE
157+
schema_id = SCHEMA_ID(@schema)
158+
159+
EXEC(@sql);
160+
";
161+
}
162+
163+
private static string GetDropSchemaSql(ISqlGenerationHelper sqlHelper, string schema)
164+
{
165+
ArgumentNullException.ThrowIfNull(sqlHelper);
166+
167+
return $"DROP SCHEMA {sqlHelper.DelimitIdentifier(schema)}";
168+
}
169+
}

src/Thinktecture.EntityFrameworkCore.SqlServer.Testing/EntityFrameworkCore/Testing/SqlServerDbContextIntegrationTests.cs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,12 +51,29 @@ protected SqlServerTestDbContextProviderBuilder<T> TestCtxProviderBuilder
5151
/// <param name="connectionString">Connection string to use.</param>
5252
/// <param name="useSharedTables">Indication whether to create new tables with a new schema or use the existing ones.</param>
5353
/// <param name="testOutputHelper">Output helper to use for logging.</param>
54+
[Obsolete($"Use the overload with '{nameof(ITestIsolationOptions)}' instead.")]
5455
protected SqlServerDbContextIntegrationTests(
5556
string connectionString,
5657
bool useSharedTables = true,
5758
ITestOutputHelper? testOutputHelper = null)
59+
: this(connectionString,
60+
useSharedTables ? ITestIsolationOptions.SharedTablesAmbientTransaction : ITestIsolationOptions.RollbackMigrationsAndCleanup,
61+
testOutputHelper)
5862
{
59-
_testCtxProviderBuilder = new SqlServerTestDbContextProviderBuilder<T>(connectionString, useSharedTables)
63+
}
64+
65+
/// <summary>
66+
/// Initializes new instance of <see cref="SqlServerDbContextIntegrationTests{T}"/>.
67+
/// </summary>
68+
/// <param name="connectionString">Connection string to use.</param>
69+
/// <param name="isolationOptions">Test isolation behavior.</param>
70+
/// <param name="testOutputHelper">Output helper to use for logging.</param>
71+
protected SqlServerDbContextIntegrationTests(
72+
string connectionString,
73+
ITestIsolationOptions isolationOptions,
74+
ITestOutputHelper? testOutputHelper = null)
75+
{
76+
_testCtxProviderBuilder = new SqlServerTestDbContextProviderBuilder<T>(connectionString, isolationOptions)
6077
.UseLogging(testOutputHelper);
6178
}
6279

0 commit comments

Comments
 (0)