Skip to content

Commit 2cee310

Browse files
authored
Object comparer assert (#17)
* Implement issue #12 * Add emulator. * ServiceBus testing #14 * Final clean-up. * Explicit declaration for GitHub build error. * Tweak for github build error. * Move ObjectComparer to Internal namespace. * Change pesky XUnit to Xunit in .sln. * Rename src/UnitTestEx.XUnit/UnitTestBase.cs to src/UnitTestEx.Xunit/UnitTestBase.cs * Rename src/UnitTestEx.XUnit/UnitTestEx.Xunit.csproj to src/UnitTestEx.Xunit/UnitTestEx.Xunit.csproj * Rename src/UnitTestEx.XUnit/XunitLogger.cs to src/UnitTestEx.Xunit/XunitLogger.cs * Rename src/UnitTestEx.XUnit/Internal/ApiTesterT.cs to src/UnitTestEx.Xunit/Internal/ApiTesterT.cs * Rename src/UnitTestEx.XUnit/Internal/FunctionTesterT.cs to src/UnitTestEx.Xunit/Internal/FunctionTesterT.cs * Rename src/UnitTestEx.XUnit/Internal/XunitTestImplementor.cs to src/UnitTestEx.Xunit/Internal/XunitTestImplementor.cs * Delete strong-name-key.snk * Recreate strongname. Co-authored-by: Eric Sibly
1 parent 94c2d5c commit 2cee310

File tree

55 files changed

+2103
-267
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

55 files changed

+2103
-267
lines changed

CHANGELOG.md

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

33
Represents the **NuGet** versions.
44

5+
## v1.0.7
6+
- *[Issue 12](https://github.com/Avanade/UnitTestEx/issues/12)*: `ObjectComparer.Assert` added for each test framework that compares two objects and will fail, and report, where there is not a match.
7+
- *[Issue 14](https://github.com/Avanade/UnitTestEx/issues/14)*: Re-introduced [`ServiceBusTriggerTester`](./src/UnitTestEx/Functions/ServiceBusTriggerTester.cs) which manages execution and automatically logs the value associated with the trigger.
8+
- *[Issue 14](https://github.com/Avanade/UnitTestEx/issues/14)*: The `ServiceBusTriggerTester.Emulate` ([`ServiceBusEmulatorTester`](./src/UnitTestEx/Functions/ServiceBusEmulatorTester.cs)) manages the execution of the `ServiceBusTriggerAttribue` function method by orchestrating Azure Service Bus integration in a similar manner as if the Azure function run-time proper had invoked.
9+
- *[PR 16](https://github.com/Avanade/UnitTestEx/pull/16)*: Support all media types in `MockHttpClientRequest`.
10+
- *Enhancement:* All `Run` methods now support a `RunAsync` where appropriate.
11+
512
## v1.0.6
613
- *[Issue 10](https://github.com/Avanade/UnitTestEx/issues/10)*: **Breaking change.** Changed the `ActionResultAssertor.AssertAccepted` and `ActionResultAssertor.AssertCreated` to assert status only; the existing value check should be performed using the `ActionResultAssertor.Assert`. Pattern now is to check status and value separately (no longer all inclusive).
714
- *[Issue 10](https://github.com/Avanade/UnitTestEx/issues/10)*: **Breaking change.** Changed the `HttpResponseMessageAssertor.AssertAccepted` and `HttpResponseMessageAssertor.AssertCreated` to assert status only; the existing value check should be performed using the `ActionResultAssertor.Assert`. Pattern now is to check status and value separately (no longer all inclusive).

Common.targets

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<Project>
2+
<PropertyGroup>
3+
<Version>1.0.7</Version>
4+
<LangVersion>preview</LangVersion>
5+
<Authors>Avanade</Authors>
6+
<Company>Avanade</Company>
7+
<Copyright>Avanade (c)</Copyright>
8+
<PackageProjectUrl>https://github.com/Avanade/UnitTestEx</PackageProjectUrl>
9+
<RepositoryUrl>https://github.com/Avanade/UnitTestEx</RepositoryUrl>
10+
<SignAssembly>true</SignAssembly>
11+
<DelaySign>false</DelaySign>
12+
<AssemblyOriginatorKeyFile>strong-name-key.snk</AssemblyOriginatorKeyFile>
13+
<RepositoryType>git</RepositoryType>
14+
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
15+
<PackageIconUrl>https://github.com/Avanade/UnitTestEx/raw/main/images/Logo256x256.png</PackageIconUrl>
16+
<PackageIcon>Logo256x256.png</PackageIcon>
17+
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
18+
<PackageLicenseExpression>MIT</PackageLicenseExpression>
19+
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
20+
<RunAnalyzersDuringBuild>true</RunAnalyzersDuringBuild>
21+
<Nullable>enable</Nullable>
22+
<EnableNETAnalyzers>true</EnableNETAnalyzers>
23+
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
24+
<IncludeSymbols>true</IncludeSymbols>
25+
<EmbedAllSources>true</EmbedAllSources>
26+
<GenerateDocumentationFile>true</GenerateDocumentationFile>
27+
<GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles>
28+
</PropertyGroup>
29+
30+
<ItemGroup>
31+
<None Include="..\..\images\Logo256x256.png" Link="Logo256x256.png">
32+
<PackagePath>\</PackagePath>
33+
<Pack>true</Pack>
34+
</None>
35+
</ItemGroup>
36+
</Project>

README.md

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ The scenarios that _UnitTestEx_ looks to address is the end-to-end unit-style te
1212

1313
- [API Controller](#API-Controller)
1414
- [HTTP-triggered Azure Function](#HTTP-triggered-Azure-Function)
15+
- [Service Bus-trigger Azure Function](#Service-Bus-trigger-Azure-Function)
1516
- [Generic Azure Function Type](#Generic-Azure-Function-Type)
1617
- [HTTP Client mocking](#HTTP-Client-mocking)
1718

@@ -48,7 +49,7 @@ test.ConfigureServices(sc => mcf.Replace(sc))
4849

4950
## HTTP-triggered Azure Function
5051

51-
Unfortunately, at time of writing, there is no `WebApplicationFactory` equivalent for Azure functions. _UnitTestEx_ looks to simulate by self-hosting the function, managing Dependency Injection (DI) configuration, and invocation of the specified method. _UnitTestEx_ when invoking verifies usage of `HttpTriggerAttribute`(https://docs.microsoft.com/en-us/azure/azure-functions/functions-bindings-http-webhook-trigger?tabs=csharp) and ensures a `Task<IActionResult>` result.
52+
Unfortunately, at time of writing, there is no `WebApplicationFactory` equivalent for Azure functions. _UnitTestEx_ looks to emulate by self-hosting the function, managing Dependency Injection (DI) configuration, and invocation of the specified method. _UnitTestEx_ when invoking verifies usage of [`HttpTriggerAttribute`](https://docs.microsoft.com/en-us/azure/azure-functions/functions-bindings-http-webhook-trigger?tabs=csharp) and ensures a `Task<IActionResult>` result.
5253

5354
The following is an [example](./tests/UnitTestEx.NUnit.Test/ProductControllerTest.cs).
5455

@@ -63,6 +64,42 @@ test.ConfigureServices(sc => mcf.Replace(sc))
6364

6465
<br/>
6566

67+
## Service Bus-trigger Azure Function
68+
69+
As above, there is currently no easy means to integration (in-process) test Azure functions that rely on the [Azure Service Bus](https://azure.microsoft.com/en-us/services/service-bus/). _UnitTestEx_ looks to emulate by self-hosting the function, managing Dependency Injection (DI) configuration, and invocation of the specified method and verifies usage of the [`ServiceBusTriggerAttribute`](https://docs.microsoft.com/en-us/azure/azure-functions/functions-bindings-service-bus-trigger?tabs=csharp).
70+
71+
The following is an [example](./tests/UnitTestEx.NUnit.Test/ServiceBusFunctionTest.cs) of invoking the function method directly passing in a `ServiceBusReceivedMessage` created using `test.CreateServiceBusMessage` (this creates a message as if coming from Service Bus).
72+
73+
``` csharp
74+
using var test = FunctionTester.Create<Startup>();
75+
test.ConfigureServices(sc => mcf.Replace(sc))
76+
.ServiceBusTrigger<ServiceBusFunction>()
77+
.Run(f => f.Run2(test.CreateServiceBusMessage(new Person { FirstName = "Bob", LastName = "Smith" }), test.Logger))
78+
.AssertSuccess();
79+
```
80+
81+
<br/>
82+
83+
### Service Bus Emulation
84+
85+
To enable in-process integration testing interacting with Azure Service Bus the [`ServiceBusTriggerTester.Emulate`](./src/UnitTestEx/Functions/ServiceBusTriggerTester.cs) method exposes the [`ServiceBusEmulatorTester`](./src/UnitTestEx/Functions/ServiceBusEmulatorTester.cs) type. This integrates directly with [Azure Service Bus](https://azure.microsoft.com/en-us/services/service-bus/) using the underlying functions configuration to determine connection string, etc. The `ServiceBus` _Queue_ or _Topic_ can be cleared (`Clear`), have test messages sent (`Send`), and then executed (`Run`).
86+
87+
The following is an [example](./tests/UnitTestEx.NUnit.Test/ServiceBusSimulationTest.cs).
88+
89+
``` csharp
90+
using var test = FunctionTester.Create<Startup>(includeUserSecrets: true);
91+
var r = test
92+
.ServiceBusTrigger<ServiceBusFunction>()
93+
.Simulate(nameof(ServiceBusFunction.Run2))
94+
.Clear()
95+
.SendValue(new Person { FirstName = "Bob", LastName = "Smith" })
96+
.Run()
97+
.AssertSuccess()
98+
.AssertMessageCompleted();
99+
```
100+
101+
<br/>
102+
66103
## Generic Azure Function Type
67104

68105
To support testing of any generic `Type` within an Azure Fuction, _UnitTestEx_ looks to simulate by self-hosting the function, managing Dependency Injection (DI) configuration, and invocation of the specified method.

UnitTestEx.sln

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11

22
Microsoft Visual Studio Solution File, Format Version 12.00
3-
# Visual Studio Version 16
4-
VisualStudioVersion = 16.0.31702.278
3+
# Visual Studio Version 17
4+
VisualStudioVersion = 17.0.32112.339
55
MinimumVisualStudioVersion = 10.0.40219.1
66
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UnitTestEx", "src\UnitTestEx\UnitTestEx.csproj", "{741ABD90-509F-41DD-89A0-97368047B9B8}"
77
EndProject
@@ -21,16 +21,19 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UnitTestEx.Api", "tests\Uni
2121
EndProject
2222
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{50CEF01C-FEC0-4AAD-96B8-45387EA360D5}"
2323
ProjectSection(SolutionItems) = preProject
24+
.gitignore = .gitignore
2425
CHANGELOG.md = CHANGELOG.md
2526
CODE_OF_CONDUCT.md = CODE_OF_CONDUCT.md
27+
Common.targets = Common.targets
2628
CONTRIBUTING.md = CONTRIBUTING.md
2729
LICENSE = LICENSE
2830
nuget-publish.ps1 = nuget-publish.ps1
2931
README.md = README.md
3032
SECURITY.md = SECURITY.md
33+
strong-name-key.snk = strong-name-key.snk
3134
EndProjectSection
3235
EndProject
33-
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UnitTestEx.Xunit", "src\UnitTestEx.XUnit\UnitTestEx.Xunit.csproj", "{F0146507-A0C4-4E7A-9CD4-DDFB56591C39}"
36+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UnitTestEx.Xunit", "src\UnitTestEx.Xunit\UnitTestEx.Xunit.csproj", "{F0146507-A0C4-4E7A-9CD4-DDFB56591C39}"
3437
EndProject
3538
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UnitTestEx.NUnit.Test", "tests\UnitTestEx.NUnit.Test\UnitTestEx.NUnit.Test.csproj", "{63F25714-C654-4FB7-B6A1-05E7197F416D}"
3639
EndProject

src/UnitTestEx.MSTest/FunctionTester.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ public static class FunctionTester
2020
/// <param name="includeUserSecrets">Indicates whether to include user secrets.</param>
2121
/// <param name="additionalConfiguration">Additional configuration values to add/override.</param>
2222
/// <returns>The <see cref="ApiTester{TEntryPoint}"/>.</returns>
23-
public static FunctionTester<TEntryPoint> Create<TEntryPoint>(bool? includeUnitTestConfiguration = null, bool? includeUserSecrets = null, IEnumerable<KeyValuePair<string, string>>? additionalConfiguration = null)
23+
public static FunctionTester<TEntryPoint> Create<TEntryPoint>(bool? includeUnitTestConfiguration = null, bool? includeUserSecrets = null, params KeyValuePair<string, string>[] additionalConfiguration)
2424
where TEntryPoint : FunctionsStartup, new()
2525
=> new(includeUnitTestConfiguration, includeUserSecrets, additionalConfiguration);
2626
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/UnitTestEx
2+
3+
using KellermanSoftware.CompareNetObjects;
4+
using System;
5+
using UnitTestEx.MSTest.Internal;
6+
7+
namespace UnitTestEx.MSTest
8+
{
9+
/// <summary>
10+
/// Deep object comparer.
11+
/// </summary>
12+
public static class ObjectComparer
13+
{
14+
/// <summary>
15+
/// Compares two objects of the same <see cref="Type"/> to each other.
16+
/// </summary>
17+
/// <param name="expected">The expected value.</param>
18+
/// <param name="actual">The actual value.</param>
19+
/// <param name="membersToIgnore">The members to ignore from the comparison.</param>
20+
public static void Assert(object? expected, object? actual, params string[] membersToIgnore)
21+
{
22+
var cr = Abstractions.ObjectComparer.Compare(expected, actual, membersToIgnore);
23+
new MSTestImplementor().AssertAreEqual(true, cr.AreEqual, cr.DifferencesString);
24+
}
25+
26+
/// <summary>
27+
/// Compares two objects of the same <see cref="Type"/> to each other.
28+
/// </summary>
29+
/// <param name="comparisonConfig">The action to enable additional <see cref="ComparisonConfig"/> configuration.</param>
30+
/// <param name="expected">The expected value.</param>
31+
/// <param name="actual">The actual value.</param>
32+
/// <param name="membersToIgnore">The members to ignore from the comparison.</param>
33+
public static void Assert(Action<ComparisonConfig> comparisonConfig, object? expected, object? actual, params string[] membersToIgnore)
34+
{
35+
var cr = Abstractions.ObjectComparer.Compare(comparisonConfig, expected, actual, membersToIgnore);
36+
new MSTestImplementor().AssertAreEqual(true, cr.AreEqual, cr.DifferencesString);
37+
}
38+
}
39+
}
Lines changed: 8 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,19 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<TargetFramework>netcoreapp3.1</TargetFramework>
5-
<RootNamespace>UnitTestEx.MSTest</RootNamespace>
6-
<Version>1.0.6</Version>
7-
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
8-
<Authors>UnitTestEx Developers</Authors>
9-
<Company>Avanade</Company>
10-
<Description>Unit Test Extensions.</Description>
11-
<LangVersion>preview</LangVersion>
12-
<Copyright>Avanade (c)</Copyright>
13-
<PackageProjectUrl>https://github.com/Avanade/UnitTestEx</PackageProjectUrl>
14-
<RepositoryUrl>https://github.com/Avanade/UnitTestEx</RepositoryUrl>
15-
<Product>UnitTestEx.MSTest</Product>
16-
<SignAssembly>true</SignAssembly>
17-
<DelaySign>false</DelaySign>
18-
<AssemblyOriginatorKeyFile>strong-name-key.snk</AssemblyOriginatorKeyFile>
19-
<PackageIconUrl>https://github.com/Avanade/UnitTestEx/raw/main/images/Logo256x256.png</PackageIconUrl>
20-
<PackageIcon>Logo256x256.png</PackageIcon>
21-
<RepositoryType>git</RepositoryType>
22-
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
23-
<PackageLicenseExpression>MIT</PackageLicenseExpression>
24-
<Title>UnitTestEx Code Generator.</Title>
25-
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
26-
<PackageTags>unittestex unit test unittest mstest nunit xunit</PackageTags>
27-
<RunAnalyzersDuringBuild>true</RunAnalyzersDuringBuild>
28-
<Nullable>enable</Nullable>
29-
<EnableNETAnalyzers>true</EnableNETAnalyzers>
30-
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
31-
<IncludeSymbols>true</IncludeSymbols>
32-
<EmbedAllSources>true</EmbedAllSources>
33-
<GenerateDocumentationFile>true</GenerateDocumentationFile>
34-
<GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles>
4+
<TargetFramework>netcoreapp3.1</TargetFramework>
5+
<RootNamespace>UnitTestEx.MSTest</RootNamespace>
6+
<Product>UnitTestEx MSTest</Product>
7+
<Title>UnitTestEx MSTest Test Extensions.</Title>
8+
<Description>UnitTestEx MSTest Test Extensions.</Description>
9+
<PackageTags>unittestex api function unit test unittest mstest</PackageTags>
3510
</PropertyGroup>
3611

3712
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
3813
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
3914
<WarningsAsErrors />
4015
</PropertyGroup>
4116

42-
<ItemGroup>
43-
<None Include="..\..\images\Logo256x256.png" Link="Logo256x256.png">
44-
<PackagePath>\</PackagePath>
45-
<Pack>true</Pack>
46-
</None>
47-
</ItemGroup>
48-
4917
<ItemGroup>
5018
<PackageReference Include="MSTest.TestFramework" Version="2.2.8" />
5119
</ItemGroup>
@@ -54,4 +22,6 @@
5422
<ProjectReference Include="..\UnitTestEx\UnitTestEx.csproj" />
5523
</ItemGroup>
5624

25+
<Import Project="..\..\Common.targets"/>
26+
5727
</Project>

src/UnitTestEx.NUnit/FunctionTester.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ public static class FunctionTester
2020
/// <param name="includeUserSecrets">Indicates whether to include user secrets.</param>
2121
/// <param name="additionalConfiguration">Additional configuration values to add/override.</param>
2222
/// <returns>The <see cref="ApiTester{TEntryPoint}"/>.</returns>
23-
public static FunctionTester<TEntryPoint> Create<TEntryPoint>(bool? includeUnitTestConfiguration = null, bool? includeUserSecrets = null, IEnumerable<KeyValuePair<string, string>>? additionalConfiguration = null)
23+
public static FunctionTester<TEntryPoint> Create<TEntryPoint>(bool? includeUnitTestConfiguration = null, bool? includeUserSecrets = null, params KeyValuePair<string, string>[] additionalConfiguration)
2424
where TEntryPoint : FunctionsStartup, new()
2525
=> new(includeUnitTestConfiguration, includeUserSecrets, additionalConfiguration);
2626
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/UnitTestEx
2+
3+
using KellermanSoftware.CompareNetObjects;
4+
using System;
5+
using UnitTestEx.NUnit.Internal;
6+
using NFI = NUnit.Framework.Internal;
7+
8+
namespace UnitTestEx.NUnit
9+
{
10+
/// <summary>
11+
/// Deep object comparer.
12+
/// </summary>
13+
public static class ObjectComparer
14+
{
15+
/// <summary>
16+
/// Compares two objects of the same <see cref="Type"/> to each other.
17+
/// </summary>
18+
/// <param name="expected">The expected value.</param>
19+
/// <param name="actual">The actual value.</param>
20+
/// <param name="membersToIgnore">The members to ignore from the comparison.</param>
21+
public static void Assert(object? expected, object? actual, params string[] membersToIgnore)
22+
{
23+
var cr = Abstractions.ObjectComparer.Compare(expected, actual, membersToIgnore);
24+
new NUnitTestImplementor(NFI.TestExecutionContext.CurrentContext).AssertAreEqual(true, cr.AreEqual, cr.DifferencesString);
25+
}
26+
27+
/// <summary>
28+
/// Compares two objects of the same <see cref="Type"/> to each other.
29+
/// </summary>
30+
/// <param name="comparisonConfig">The action to enable additional <see cref="ComparisonConfig"/> configuration.</param>
31+
/// <param name="expected">The expected value.</param>
32+
/// <param name="actual">The actual value.</param>
33+
/// <param name="membersToIgnore">The members to ignore from the comparison.</param>
34+
public static void Assert(Action<ComparisonConfig> comparisonConfig, object? expected, object? actual, params string[] membersToIgnore)
35+
{
36+
var cr = Abstractions.ObjectComparer.Compare(comparisonConfig, expected, actual, membersToIgnore);
37+
new NUnitTestImplementor(NFI.TestExecutionContext.CurrentContext).AssertAreEqual(true, cr.AreEqual, cr.DifferencesString);
38+
}
39+
}
40+
}

0 commit comments

Comments
 (0)