Skip to content

Commit 5def8f3

Browse files
authored
v5.9.0 (#99)
- *Enhancement:* Added `WithGenericTester` (_MSTest_ and _NUnit_ only) class to enable class-level generic tester usage versus one-off. - *Enhancement:* Added `TesterBase.UseScopedTypeSetUp()` to enable a function that will be executed directly before each `ScopedTypeTester{TService}` is instantiated to allow standardized/common set up to occur.
1 parent 6ca7cfb commit 5def8f3

File tree

8 files changed

+195
-2
lines changed

8 files changed

+195
-2
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
Represents the **NuGet** versions.
44

5+
## v5.9.0
6+
- *Enhancement:* Added `WithGenericTester` (_MSTest_ and _NUnit_ only) class to enable class-level generic tester usage versus one-off.
7+
- *Enhancement:* Added `TesterBase.UseScopedTypeSetUp()` to enable a function that will be executed directly before each `ScopedTypeTester{TService}` is instantiated to allow standardized/common set up to occur.
8+
59
## v5.8.0
610
- *Enhancement:* Extended the `MockHttpClientResponse.With*` methods to support optional _media type_ parameter to enable specification of the `Content-Type` header value.
711
- *Enhancement:* Added `HttpResponseMessageAssertor.AssertContentTypeProblemJson` to enable asserting that the content type is `application/problem+json`.

Common.targets

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<Project>
22
<PropertyGroup>
3-
<Version>5.8.0</Version>
3+
<Version>5.9.0</Version>
44
<LangVersion>preview</LangVersion>
55
<Authors>Avanade</Authors>
66
<Company>Avanade</Company>
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/UnitTestEx
2+
3+
using System;
4+
using UnitTestEx.Generic;
5+
6+
#pragma warning disable IDE0130 // Namespace does not match folder structure
7+
namespace UnitTestEx
8+
#pragma warning restore IDE0130 // Namespace does not match folder structure
9+
{
10+
/// <summary>
11+
/// Provides a shared <see cref="Test"/> <see cref="GenericTester{TEntryPoint}"/> to enable usage of the same underlying instance across multiple tests.
12+
/// </summary>
13+
/// <typeparam name="TEntryPoint">The generic startup <see cref="Type"/>.</typeparam>
14+
/// <remarks>Implements <see cref="IDisposable"/> to automatically dispose of the <see cref="Test"/> instance to release all resources.
15+
/// <para>Be aware that using may result in cross-test contamination.</para></remarks>
16+
public abstract class WithGenericTester<TEntryPoint> : IDisposable where TEntryPoint : class
17+
{
18+
private bool _disposed;
19+
private GenericTester<TEntryPoint>? _genericTester = GenericTester.Create<TEntryPoint>();
20+
21+
/// <summary>
22+
/// Gets the underlying <see cref="GenericTester{TEntryPoint}"/> for testing.
23+
/// </summary>
24+
public GenericTester<TEntryPoint> Test => _genericTester ?? throw new ObjectDisposedException(nameof(Test));
25+
26+
/// <summary>
27+
/// Dispose of all resources.
28+
/// </summary>
29+
/// <param name="disposing">Indicates whether from the <see cref="Dispose()"/>.</param>
30+
protected virtual void Dispose(bool disposing)
31+
{
32+
if (!_disposed)
33+
{
34+
if (disposing)
35+
{
36+
_genericTester?.Dispose();
37+
_genericTester = null;
38+
}
39+
40+
_disposed = true;
41+
}
42+
}
43+
44+
/// <inheritdoc/>
45+
public void Dispose()
46+
{
47+
Dispose(disposing: true);
48+
GC.SuppressFinalize(this);
49+
}
50+
}
51+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/UnitTestEx
2+
3+
using System;
4+
using UnitTestEx.Generic;
5+
6+
#pragma warning disable IDE0130 // Namespace does not match folder structure
7+
namespace UnitTestEx
8+
#pragma warning restore IDE0130 // Namespace does not match folder structure
9+
{
10+
/// <summary>
11+
/// Provides a shared <see cref="Test"/> <see cref="GenericTester{TEntryPoint}"/> to enable usage of the same underlying instance across multiple tests.
12+
/// </summary>
13+
/// <typeparam name="TEntryPoint">The generic startup <see cref="Type"/>.</typeparam>
14+
/// <remarks>Implements <see cref="IDisposable"/> to automatically dispose of the <see cref="Test"/> instance to release all resources.
15+
/// <para>Be aware that using may result in cross-test contamination.</para></remarks>
16+
public abstract class WithGenericTester<TEntryPoint> : IDisposable where TEntryPoint : class
17+
{
18+
private bool _disposed;
19+
private GenericTester<TEntryPoint>? _genericTester = GenericTester.Create<TEntryPoint>();
20+
21+
/// <summary>
22+
/// Gets the underlying <see cref="GenericTester{TEntryPoint}"/> for testing.
23+
/// </summary>
24+
public GenericTester<TEntryPoint> Test => _genericTester ?? throw new ObjectDisposedException(nameof(Test));
25+
26+
/// <summary>
27+
/// Dispose of all resources.
28+
/// </summary>
29+
/// <param name="disposing">Indicates whether from the <see cref="Dispose()"/>.</param>
30+
protected virtual void Dispose(bool disposing)
31+
{
32+
if (!_disposed)
33+
{
34+
if (disposing)
35+
{
36+
_genericTester?.Dispose();
37+
_genericTester = null;
38+
}
39+
40+
_disposed = true;
41+
}
42+
}
43+
44+
/// <inheritdoc/>
45+
public void Dispose()
46+
{
47+
Dispose(disposing: true);
48+
GC.SuppressFinalize(this);
49+
}
50+
}
51+
}

src/UnitTestEx/Abstractions/TesterBaseT.cs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using Moq;
66
using System;
77
using System.Collections.Generic;
8+
using System.Diagnostics.Metrics;
89
using System.Runtime.CompilerServices;
910
using System.Threading.Tasks;
1011
using UnitTestEx.Hosting;
@@ -18,6 +19,8 @@ namespace UnitTestEx.Abstractions
1819
/// <typeparam name="TSelf">The <see cref="TesterBase{TSelf}"/> to support inheriting fluent-style method-chaining.</typeparam>
1920
public abstract class TesterBase<TSelf> : TesterBase where TSelf : TesterBase<TSelf>
2021
{
22+
private Func<IServiceProvider, Task>? _scopedSetUpAsync;
23+
2124
/// <summary>
2225
/// Initializes a new instance of the <see cref="TesterBase{TSelf}"/> class.
2326
/// </summary>
@@ -116,6 +119,17 @@ public TSelf UseAdditionalConfiguration(IEnumerable<KeyValuePair<string, string?
116119
/// <remarks>Usage will result in a <see cref="TesterBase.ResetHost()"/>.</remarks>
117120
public TSelf UseAdditionalConfiguration(string key, string? value) => UseAdditionalConfiguration([new KeyValuePair<string, string?>(key, value)]);
118121

122+
/// <summary>
123+
/// Updates (replaces) the function that will be executed directly before each <see cref="ScopedTypeTester{TService}"/> is instantiated to allow standardized/common set up to occur.
124+
/// </summary>
125+
/// <param name="setupAsync">The set-up function.</param>
126+
/// <returns>The <typeparamref name="TSelf"/> to support fluent-style method-chaining.</returns>
127+
public TSelf UseScopedTypeSetUp(Func<IServiceProvider, Task> setupAsync)
128+
{
129+
_scopedSetUpAsync = setupAsync;
130+
return (TSelf)this;
131+
}
132+
119133
/// <summary>
120134
/// Resets the underlying host to instantiate a new instance.
121135
/// </summary>
@@ -426,6 +440,7 @@ public TSelf ScopedType<TService>(Action<ScopedTypeTester<TService>> scopedTeste
426440
ArgumentNullException.ThrowIfNull(scopedTester);
427441

428442
using var scope = HostExecutionWrapper(Services.CreateScope);
443+
InvokeScopedTesterSetUp(scope);
429444
var tester = new ScopedTypeTester<TService>(this, scope.ServiceProvider, scope.ServiceProvider.CreateInstance<TService>(serviceKey));
430445
scopedTester(tester);
431446
return (TSelf)this;
@@ -442,6 +457,7 @@ public TSelf ScopedType<TService>(Func<IServiceProvider, TService> serviceFactor
442457
{
443458
ArgumentNullException.ThrowIfNull(scopedTester);
444459
using var scope = HostExecutionWrapper(Services.CreateScope);
460+
InvokeScopedTesterSetUp(scope);
445461
var tester = new ScopedTypeTester<TService>(this, scope.ServiceProvider, serviceFactory(scope.ServiceProvider));
446462
scopedTester(tester);
447463
return (TSelf)this;
@@ -462,6 +478,7 @@ public TSelf ScopedType<TService>(Func<ScopedTypeTester<TService>, Task> scopedT
462478
ArgumentNullException.ThrowIfNull(scopedTesterAsync);
463479

464480
using var scope = HostExecutionWrapper(Services.CreateScope);
481+
InvokeScopedTesterSetUp(scope);
465482
var tester = new ScopedTypeTester<TService>(this, scope.ServiceProvider, scope.ServiceProvider.CreateInstance<TService>(serviceKey));
466483
scopedTesterAsync(tester).GetAwaiter().GetResult();
467484
return (TSelf)this;
@@ -481,6 +498,7 @@ public TSelf ScopedType<TService>(Func<IServiceProvider, TService> serviceFactor
481498
{
482499
ArgumentNullException.ThrowIfNull(scopedTesterAsync);
483500
using var scope = HostExecutionWrapper(Services.CreateScope);
501+
InvokeScopedTesterSetUp(scope);
484502
var tester = new ScopedTypeTester<TService>(this, scope.ServiceProvider, serviceFactory(scope.ServiceProvider));
485503
scopedTesterAsync(tester).GetAwaiter().GetResult();
486504
return (TSelf)this;
@@ -500,6 +518,7 @@ public TSelf ScopedType<TService>(Func<ScopedTypeTester<TService>, ValueTask> sc
500518
ArgumentNullException.ThrowIfNull(scopedTesterAsync);
501519

502520
using var scope = HostExecutionWrapper(Services.CreateScope);
521+
InvokeScopedTesterSetUp(scope);
503522
var tester = new ScopedTypeTester<TService>(this, scope.ServiceProvider, scope.ServiceProvider.CreateInstance<TService>(serviceKey));
504523
scopedTesterAsync(tester).AsTask().GetAwaiter().GetResult();
505524
return (TSelf)this;
@@ -517,11 +536,21 @@ public TSelf ScopedType<TService>(Func<IServiceProvider, TService> serviceFactor
517536
{
518537
ArgumentNullException.ThrowIfNull(scopedTesterAsync);
519538
using var scope = HostExecutionWrapper(Services.CreateScope);
539+
InvokeScopedTesterSetUp(scope);
520540
var tester = new ScopedTypeTester<TService>(this, scope.ServiceProvider, serviceFactory(scope.ServiceProvider));
521541
scopedTesterAsync(tester).AsTask().GetAwaiter().GetResult();
522542
return (TSelf)this;
523543
}
524544

525545
#endif
546+
547+
/// <summary>
548+
/// Executes the scoped tester set up.
549+
/// </summary>
550+
private void InvokeScopedTesterSetUp(IServiceScope? scope)
551+
{
552+
if (scope is not null && _scopedSetUpAsync is not null)
553+
_scopedSetUpAsync(scope.ServiceProvider).GetAwaiter().GetResult();
554+
}
526555
}
527556
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
using Microsoft.Extensions.Configuration;
2+
using Microsoft.Extensions.DependencyInjection;
3+
using Microsoft.VisualStudio.TestTools.UnitTesting;
4+
5+
namespace UnitTestEx.MSTest.Test.Other
6+
{
7+
[TestClass]
8+
public class WithGenericTesterTest : WithGenericTester<object>
9+
{
10+
[TestMethod]
11+
public void Run_Success()
12+
{
13+
var c = Test.Services.GetService<IConfiguration>();
14+
15+
Test.Run(() => 1)
16+
.AssertSuccess()
17+
.AssertValue(1);
18+
}
19+
20+
[TestMethod]
21+
public void Run_Success_AssertJSON()
22+
{
23+
Test.Run(() => 1)
24+
.AssertSuccess()
25+
.AssertJson("1");
26+
}
27+
}
28+
}

tests/UnitTestEx.NUnit.Test/Other/GenericTest.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,9 @@ public class EntryPoint
110110

111111
//public void ConfigureServices(IServiceCollection services) { }
112112

113-
public void ConfigureApplication(IHostApplicationBuilder builder) => builder.Services.AddSingleton<Gin>();
113+
public void ConfigureApplication(IHostApplicationBuilder builder)
114+
{
115+
builder.Services.AddSingleton<Gin>();
116+
}
114117
}
115118
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
using Microsoft.Extensions.Configuration;
2+
using Microsoft.Extensions.DependencyInjection;
3+
using NUnit.Framework;
4+
5+
namespace UnitTestEx.NUnit.Test.Other
6+
{
7+
public class WithGenericTesterTest : WithGenericTester<EntryPoint>
8+
{
9+
[Test]
10+
public void Run_Success()
11+
{
12+
var c = Test.Services.GetService<IConfiguration>();
13+
14+
Test.Run(() => 1)
15+
.AssertSuccess()
16+
.AssertValue(1);
17+
}
18+
19+
[Test]
20+
public void Run_Success_AssertJSON()
21+
{
22+
Test.Run(() => 1)
23+
.AssertSuccess()
24+
.AssertJson("1");
25+
}
26+
}
27+
}

0 commit comments

Comments
 (0)