├── .github
└── workflows
│ └── ci.yml
├── .gitignore
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── Common.targets
├── LICENSE
├── README.md
├── SECURITY.md
├── UnitTestEx.sln
├── images
└── Logo256x256.png
├── nuget-publish.ps1
├── src
├── UnitTestEx.Azure.Functions
│ ├── Azure
│ │ └── Functions
│ │ │ ├── FunctionTesterBase.cs
│ │ │ ├── FunctionTesterT.cs
│ │ │ ├── HttpTriggerTester.cs
│ │ │ ├── RouteCheckOption.cs
│ │ │ ├── ServiceBusMessageActionStatus.cs
│ │ │ ├── ServiceBusTriggerTester.cs
│ │ │ ├── TestSetUp.cs
│ │ │ ├── WebJobsServiceBusMessageActionsAssertor.cs
│ │ │ ├── WebJobsServiceBusSessionMessageActionsAssertor.cs
│ │ │ └── WorkerServiceBusMessageActionsAssertor.cs
│ ├── ExtensionMethods.cs
│ ├── FunctionTester.cs
│ ├── UnitTestEx.Azure.Functions.csproj
│ └── strong-name-key.snk
├── UnitTestEx.Azure.ServiceBus
│ ├── ExtensionMethods.cs
│ ├── UnitTestEx.Azure.ServiceBus.csproj
│ └── strong-name-key.snk
├── UnitTestEx.MSTest
│ ├── Internal
│ │ ├── MSTestImplementor.cs
│ │ └── OneOffTestSetUp.cs
│ ├── UnitTestEx.MSTest.csproj
│ ├── WithApiTester.cs
│ └── strong-name-key.snk
├── UnitTestEx.NUnit
│ ├── Internal
│ │ ├── NUnitTestImplementor.cs
│ │ └── OneOffTestSetUp.cs
│ ├── UnitTestEx.NUnit.csproj
│ ├── WithApiTester.cs
│ └── strong-name-key.snk
├── UnitTestEx.Xunit
│ ├── ApiTestFixture.cs
│ ├── Internal
│ │ ├── XunitLocalTestImplementor.cs
│ │ └── XunitTestImplementor.cs
│ ├── UnitTestBase.cs
│ ├── UnitTestEx.Xunit.csproj
│ ├── WithApiTester.cs
│ └── strong-name-key.snk
└── UnitTestEx
│ ├── Abstractions
│ ├── OneOffTestSetUpAttribute.cs
│ ├── OneOffTestSetUpBase.cs
│ ├── TestFrameworkImplementor.cs
│ ├── TestSetUpException.cs
│ ├── TestSharedState.cs
│ ├── TesterBase.cs
│ ├── TesterBaseT.cs
│ └── TesterExtensionsConfig.cs
│ ├── ApiError.cs
│ ├── ApiTester.cs
│ ├── AspNetCore
│ ├── ApiTesterBase.cs
│ ├── ApiTesterT.cs
│ ├── BypassAuthorizationHandler.cs
│ ├── ControllerTester.cs
│ ├── HttpTester.cs
│ ├── HttpTesterBase.cs
│ ├── HttpTesterBaseT.cs
│ ├── HttpTesterBaseT2.cs
│ └── HttpTesterT.cs
│ ├── Assertors
│ ├── ActionResultAssertor.cs
│ ├── Assertor.cs
│ ├── AssertorBase.cs
│ ├── AssertorBaseT.cs
│ ├── HttpResponseMessageAssertor.cs
│ ├── HttpResponseMessageAssertorBase.cs
│ ├── HttpResponseMessageAssertorBaseT.cs
│ ├── HttpResponseMessageAssertorT.cs
│ ├── ValueAssertor.cs
│ └── VoidAssertor.cs
│ ├── Expectations
│ ├── AssertArgs.cs
│ ├── ErrorExpectations.cs
│ ├── ExceptionExpectations.cs
│ ├── ExpectationsArranger.cs
│ ├── ExpectationsBase.cs
│ ├── ExpectationsBaseT.cs
│ ├── ExpectationsExtensions.cs
│ ├── HttpResponseMessageExpectations.cs
│ ├── IExpectations.cs
│ ├── IHttpResponseMessageExpectations.cs
│ ├── IValueExpectations.cs
│ ├── LoggerExpectations.cs
│ └── ValueExpectations.cs
│ ├── ExtensionMethods.cs
│ ├── Generic
│ ├── GenericTesterBase.cs
│ ├── GenericTesterBaseT.cs
│ ├── GenericTesterCore.cs
│ ├── GenericTesterT.cs
│ └── GenericTesterT2.cs
│ ├── GenericTester.cs
│ ├── Hosting
│ ├── EntryPoint.cs
│ ├── HostTesterBase.cs
│ └── TypeTester.cs
│ ├── Json
│ ├── IJsonSerializer.cs
│ ├── JsonElementComparer.cs
│ ├── JsonElementComparerOptions.cs
│ ├── JsonElementComparerResult.cs
│ ├── JsonElementComparison.cs
│ ├── JsonElementDifference.cs
│ ├── JsonElementDifferenceType.cs
│ ├── JsonExtensions.cs
│ ├── JsonSerializer.cs
│ └── JsonWriteFormat.cs
│ ├── Logging
│ ├── LoggerBase.cs
│ ├── SharedStateLogger.cs
│ └── TestFrameworkImplementerLogger.cs
│ ├── MockHttpClientException.cs
│ ├── MockHttpClientFactory.cs
│ ├── Mocking
│ ├── MockHttpClient.cs
│ ├── MockHttpClientFactory.cs
│ ├── MockHttpClientHandler.cs
│ ├── MockHttpClientRequest.cs
│ ├── MockHttpClientRequestBody.cs
│ ├── MockHttpClientRequestRule.cs
│ ├── MockHttpClientResponse.cs
│ └── MockHttpClientResponseSequence.cs
│ ├── ObjectComparer.cs
│ ├── Resource.cs
│ ├── Schema
│ └── mock.unittestex.json
│ ├── SystemExtensionMethods.cs
│ ├── TestSetUp.cs
│ ├── UnitTestEx.csproj
│ └── strong-name-key.snk
└── tests
├── UnitTestEx.Api
├── Controllers
│ ├── PersonController.cs
│ ├── ProductController.cs
│ └── TestController.cs
├── Models
│ └── Person.cs
├── Program.cs
├── Startup.cs
├── UnitTestEx.Api.csproj
├── appsettings.Development.json
└── appsettings.json
├── UnitTestEx.Function
├── .gitignore
├── PersonFunction.cs
├── ProductFunction.cs
├── Properties
│ ├── serviceDependencies.json
│ └── serviceDependencies.local.json
├── ServiceBusFunction.cs
├── Startup.cs
├── UnitTestEx.Function.csproj
└── host.json
├── UnitTestEx.IsolatedFunction
├── .gitignore
├── HttpFunction.cs
├── Program.cs
├── Properties
│ ├── serviceDependencies.json
│ └── serviceDependencies.local.json
├── ServiceBusFunction.cs
├── Startup.cs
├── TimerFunction.cs
├── UnitTestEx.IsolatedFunction.csproj
└── host.json
├── UnitTestEx.MSTest.Test
├── ExpressionTest.cs
├── MockHttpClientTest.cs
├── Model
│ ├── Person.cs
│ └── Person2.cs
├── NewtonsoftJsonSerializer.cs
├── Other
│ ├── GenericTest.cs
│ ├── JsonElementComparerTest.cs
│ ├── LoggerTest.cs
│ └── ObjectComparerTest.cs
├── PersonControllerTest.cs
├── PersonFunctionTest.cs
├── ProductControllerTest.cs
├── ProductFunctionTest.cs
├── Resources
│ ├── FunctionTest-ValidJsonResource.json
│ └── MockHttpClientTest-UriAndBody_WithJsonResponse3.json
├── ServiceBusFunctionTest.cs
├── UnitTestEx.MSTest.Test.csproj
└── appsettings.unittest.json
├── UnitTestEx.NUnit.Test
├── IsolatedFunctionTest.cs
├── MockHttpClientTest.cs
├── Model
│ ├── Person.cs
│ └── Person2.cs
├── Other
│ ├── ExpectationsTest.cs
│ ├── GenericTest.cs
│ ├── LoggerTest.cs
│ ├── ObjectComparerTest.cs
│ └── OneOffTestSetUpTest.cs
├── PersonControllerTest.cs
├── PersonFunctionTest.cs
├── ProductControllerTest.cs
├── ProductFunctionTest.cs
├── Resources
│ ├── FunctionTest-ValidJsonResource.json
│ ├── MockHttpClientTest-UriAndBody_WithJsonResponse3.json
│ ├── mock.unittestex.yaml
│ └── sequence.unittestex.yaml
├── ServiceBusFunctionTest.cs
├── UnitTestEx.NUnit.Test.csproj
└── appsettings.unittest.json
└── UnitTestEx.Xunit.Test
├── MockHttpClientTest.cs
├── Model
├── Person.cs
└── Person2.cs
├── Other
├── GenericTest.cs
├── LoggerTest.cs
└── ObjectComparerTest.cs
├── PersonControllerTest.cs
├── PersonFunctionTest.cs
├── ProductApiTestFixture.cs
├── ProductControllerTest.cs
├── ProductFunctionTest.cs
├── Resources
├── FunctionTest-ValidJsonResource.json
└── MockHttpClientTest-UriAndBody_WithJsonResponse3.json
├── ServiceBusFunctionTest.cs
├── UnitTestEx.Xunit.Test.csproj
└── appsettings.unittest.json
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | push:
5 | branches: [ main ]
6 | pull_request:
7 | branches: [ main ]
8 |
9 | jobs:
10 | build:
11 | runs-on: ubuntu-latest
12 |
13 | steps:
14 | - uses: actions/checkout@v2
15 |
16 | - name: Setup .NET
17 | uses: actions/setup-dotnet@v1
18 | with:
19 | dotnet-version: |
20 | 6.0.x
21 | 7.0.x
22 | 8.0.x
23 | 9.0.x
24 |
25 | - name: Restore dependencies
26 | run: dotnet restore
27 |
28 | - name: Build
29 | run: dotnet build --no-restore
30 |
31 | - name: Explicit MSTest test
32 | run: |
33 | cp tests/UnitTestEx.Api/bin/Debug/net6.0/UnitTestEx.Api.deps.json tests/UnitTestEx.MSTest.Test/bin/Debug/net6.0
34 | cd tests/UnitTestEx.MSTest.Test/bin/Debug/net6.0
35 | dotnet test UnitTestEx.MSTest.Test.dll --no-build --verbosity normal
36 |
37 | - name: Explicit NUnit test
38 | run: |
39 | cp tests/UnitTestEx.Api/bin/Debug/net8.0/UnitTestEx.Api.deps.json tests/UnitTestEx.NUnit.Test/bin/Debug/net8.0
40 | cd tests/UnitTestEx.NUnit.Test/bin/Debug/net8.0
41 | dotnet test UnitTestEx.NUnit.Test.dll --no-build --verbosity normal
42 |
43 | - name: Explicit Xunit test
44 | run: |
45 | cp tests/UnitTestEx.Api/bin/Debug/net8.0/UnitTestEx.Api.deps.json tests/UnitTestEx.Xunit.Test/bin/Debug/net8.0
46 | cd tests/UnitTestEx.Xunit.Test/bin/Debug/net8.0
47 | dotnet test UnitTestEx.Xunit.Test.dll --no-build --verbosity normal --tests MockHttpClientTest
48 | dotnet test UnitTestEx.Xunit.Test.dll --no-build --verbosity normal --tests PersonFunctionTest
49 | dotnet test UnitTestEx.Xunit.Test.dll --no-build --verbosity normal --tests ProductFunctionTest
50 | dotnet test UnitTestEx.Xunit.Test.dll --no-build --verbosity normal --tests ServiceBusFunctionTest
51 | dotnet test UnitTestEx.Xunit.Test.dll --no-build --verbosity normal --tests PersonControllerTest
52 | dotnet test UnitTestEx.Xunit.Test.dll --no-build --verbosity normal --tests ProductControllerTest
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | [](https://avanade.github.io/code-of-conduct/)
2 |
3 | This project adopts the [Contributor Covenant Code of Conduct](https://avanade.github.io/code-of-conduct/).
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to this project
2 |
3 | This project welcomes contributions and suggestions. By contributing, you confirm that you have the right to, and actually do, grant us the rights to use your contribution. More information below.
4 |
5 | Please feel free to contribute code, ideas, improvements, and patches - we've added some general guidelines and information below, and you can propose changes to this document in a pull request.
6 |
7 | This project has adopted the [Contributor Covenant Code of Conduct](https://avanade.github.io/code-of-conduct/).
8 |
9 |
10 |
11 | ## Rights to your contributions
12 | By contributing to this project, you:
13 | - Agree that you have authored 100% of the content;
14 | - Agree that you have the necessary rights to the content;
15 | - Agree that you have received the necessary permissions from your employer to make the contributions (if applicable);
16 | - Agree that the content you contribute may be provided under the Project license(s);
17 | - Agree that, if you did not author 100% of the content, the appropriate licenses and copyrights have been added along with any other necessary attribution.
18 |
19 |
20 |
21 | ## Code of Conduct
22 | This project, and people participating in it, are governed by our [code of conduct](https://avanade.github.io/code-of-conduct/). By taking part, we expect you to try your best to uphold this code of conduct. If you have concerns about unacceptable behaviour, please contact the community leaders responsible for enforcement at
23 | [ospo@avanade.com](ospo@avanade.com).
24 |
25 |
26 |
27 | ## Coding guidelines
28 |
29 | The most general guideline is that we use all the VS default settings in terms of code formatting; if in doubt, follow the coding convention of the existing code base.
30 | 1. Use four spaces of indentation (no tabs).
31 | 2. Use `_camelCase` for private fields.
32 | 3. Avoid `this.` unless absolutely necessary.
33 | 4. Always specify member visibility, even if it's the default (i.e. `private string _foo;` not `string _foo;`).
34 | 5. Open-braces (`{`) go on a new line (an `if` with single-line statement does not need braces).
35 | 6. Use any language features available to you (expression-bodied members, throw expressions, tuples, etc.) as long as they make for readable, manageable code.
36 | 7. All methods and properties must include the [XML documentation comments](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/xmldoc/xml-documentation-comments). Private methods and properties only need to specifiy the [summary](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/xmldoc/summary) as a minimum.
37 | 8. Breaking changes should be avoided where possible; however, to minimize lifetime costs they are acceptable (also avoid alternatives unless additive to minimize codebase bloat). For the most part be guided by [Semantic Versioning 2.0.0](https://semver.org/).
38 |
39 | For further guidance see ASP.NET Core [Engineering guidelines](https://github.com/aspnet/AspNetCore/wiki/Engineering-guidelines).
40 |
41 |
42 |
43 | ## Tests
44 |
45 | We use [`NUnit`](https://github.com/nunit/nunit) for all unit testing.
46 |
47 | - Tests need to be provided for every bug/feature that is completed.
48 | - Tests only need to be present for issues that need to be verified by QA (for example, not tasks).
49 | - If there is a scenario that is far too hard to test there does not need to be a test for it.
50 | - "Too hard" is determined by the team as a whole.
51 |
52 | We understand there is more work to be performed in generating a higher level of code coverage; this technical debt is on the backlog.
53 |
54 |
55 |
56 | ## Code reviews and checkins
57 |
58 | To help ensure that only the highest quality code makes its way into the project, please submit all your code changes to GitHub as PRs. This includes runtime code changes, unit test updates, and updates to the end-to-end demo.
59 |
60 | For example, sending a PR for just an update to a unit test might seem like a waste of time but the unit tests are just as important as the product code and as such, reviewing changes to them is also just as important. This also helps create visibility for your changes so that others can observe what is going on.
61 |
62 | The advantages are numerous: improving code quality, more visibility on changes and their potential impact, avoiding duplication of effort, and creating general awareness of progress being made in various areas.
--------------------------------------------------------------------------------
/Common.targets:
--------------------------------------------------------------------------------
1 |
2 |
3 | 5.5.0
4 | preview
5 | Avanade
6 | Avanade
7 | Avanade (c)
8 | https://github.com/Avanade/UnitTestEx
9 | https://github.com/Avanade/UnitTestEx
10 | true
11 | false
12 | strong-name-key.snk
13 | git
14 | false
15 | https://github.com/Avanade/UnitTestEx/raw/main/images/Logo256x256.png
16 | Logo256x256.png
17 | true
18 | MIT
19 | $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb
20 | true
21 | enable
22 | true
23 | true
24 | snupkg
25 | true
26 | true
27 | true
28 | true
29 | true
30 | false
31 |
32 |
33 |
34 |
35 | \
36 | true
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020-2021 Avanade Inc
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Avanade responsible disclosure
2 |
3 | Avanade operates a 🔐 [Responsible disclosure policy](https://www.avanade.com/en/about-avanade/approach/trust-center/responsible-disclosure).
--------------------------------------------------------------------------------
/images/Logo256x256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Avanade/UnitTestEx/d80ff2987e1f9aab282aa1c80638ba6015fbff0a/images/Logo256x256.png
--------------------------------------------------------------------------------
/src/UnitTestEx.Azure.Functions/Azure/Functions/FunctionTesterT.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/UnitTestEx
2 |
3 | using System.Collections.Generic;
4 | using UnitTestEx.Abstractions;
5 |
6 | namespace UnitTestEx.Azure.Functions
7 | {
8 | ///
9 | /// Provides the NUnit implementation.
10 | ///
11 | /// The Function startup .
12 | /// Indicates whether to include 'appsettings.unittest.json' configuration file.
13 | /// Indicates whether to include user secrets.
14 | /// Additional configuration values to add/override.
15 | public class FunctionTester(bool? includeUnitTestConfiguration, bool? includeUserSecrets, IEnumerable>? additionalConfiguration)
16 | : FunctionTesterBase>(TestFrameworkImplementor.Create(), includeUnitTestConfiguration, includeUserSecrets, additionalConfiguration) where TEntryPoint : class, new()
17 | { }
18 | }
--------------------------------------------------------------------------------
/src/UnitTestEx.Azure.Functions/Azure/Functions/RouteCheckOption.cs:
--------------------------------------------------------------------------------
1 | namespace UnitTestEx.Azure.Functions
2 | {
3 | ///
4 | /// Represents the route check option for the .
5 | ///
6 | public enum RouteCheckOption
7 | {
8 | ///
9 | /// No route check is required,
10 | ///
11 | None,
12 |
13 | ///
14 | /// The route should match the specified path excluding any query string.
15 | ///
16 | Path,
17 |
18 | ///
19 | /// The route should match the specified path including the query string.
20 | ///
21 | PathAndQuery,
22 |
23 | ///
24 | /// The route should start with the specified path and query string.
25 | ///
26 | PathAndQueryStartsWith,
27 |
28 | ///
29 | /// The route query (ignore path) should match the specified query string.
30 | ///
31 | Query,
32 |
33 | ///
34 | /// The route query (ignore path) should start with the specified query string.
35 | ///
36 | QueryStartsWith
37 | }
38 | }
--------------------------------------------------------------------------------
/src/UnitTestEx.Azure.Functions/Azure/Functions/ServiceBusMessageActionStatus.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/UnitTestEx
2 |
3 | using Microsoft.Azure.WebJobs.ServiceBus;
4 |
5 | namespace UnitTestEx.Azure.Functions
6 | {
7 | ///
8 | /// Represents the status.
9 | ///
10 | public enum ServiceBusMessageActionStatus
11 | {
12 | ///
13 | /// Indicates that no action occured.
14 | ///
15 | None,
16 |
17 | ///
18 | /// Indicates that the AbandonMessageAsync was invoked.
19 | ///
20 | Abandon,
21 |
22 | ///
23 | /// Indicates that the CompleteMessageAsync was invoked.
24 | ///
25 | Complete,
26 |
27 | ///
28 | /// Indicates that the DeadLetterMessageAsync was invoked.
29 | ///
30 | DeadLetter,
31 |
32 | ///
33 | /// Indicates that the DeferMessageAsync was invoked.
34 | ///
35 | Defer,
36 |
37 | ///
38 | /// Indicates that the RenewMessageLockAsync was invoked.
39 | ///
40 | Renew
41 | }
42 | }
--------------------------------------------------------------------------------
/src/UnitTestEx.Azure.Functions/Azure/Functions/TestSetUp.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/UnitTestEx
2 |
3 |
4 | // Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/UnitTestEx
5 |
6 | namespace UnitTestEx.Azure.Functions
7 | {
8 | ///
9 | /// Extends the .
10 | ///
11 | public static class TestSetUp
12 | {
13 | ///
14 | /// Indicates whether to include 'appsettings.unittest.json' configuration file when the host starts; defaults to true.
15 | ///
16 | public static bool FunctionTesterIncludeUnitTestConfiguration { get; set; } = true;
17 |
18 | ///
19 | /// Indicates whether to include user secrets configuration when the host starts; defaults to false.
20 | ///
21 | public static bool FunctionTesterIncludeUserSecrets { get; set; }
22 | }
23 | }
--------------------------------------------------------------------------------
/src/UnitTestEx.Azure.Functions/FunctionTester.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/UnitTestEx
2 |
3 | using System;
4 | using System.Collections.Generic;
5 | using UnitTestEx.Azure.Functions;
6 |
7 | namespace UnitTestEx
8 | {
9 | ///
10 | /// Provides the Function testing capability.
11 | ///
12 | public static class FunctionTester
13 | {
14 | ///
15 | /// Creates a new instance of the class.
16 | ///
17 | /// The Function startup .
18 | /// Indicates whether to include 'appsettings.unittest.json' configuration file.
19 | /// Indicates whether to include user secrets.
20 | /// Additional configuration values to add/override.
21 | /// The .
22 | public static FunctionTester Create(bool? includeUnitTestConfiguration = null, bool? includeUserSecrets = null, IEnumerable>? additionalConfiguration = null)
23 | where TEntryPoint : class, new()
24 | => new(includeUnitTestConfiguration, includeUserSecrets, additionalConfiguration);
25 | }
26 | }
--------------------------------------------------------------------------------
/src/UnitTestEx.Azure.Functions/UnitTestEx.Azure.Functions.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net6.0;net7.0;net8.0;net9.0
5 | UnitTestEx
6 | UnitTestEx
7 | UnitTestEx Azure Functions Test Extensions.
8 | UnitTestEx Test Extensions.
9 | unittestex azure function unit test unittest
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/src/UnitTestEx.Azure.Functions/strong-name-key.snk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Avanade/UnitTestEx/d80ff2987e1f9aab282aa1c80638ba6015fbff0a/src/UnitTestEx.Azure.Functions/strong-name-key.snk
--------------------------------------------------------------------------------
/src/UnitTestEx.Azure.ServiceBus/UnitTestEx.Azure.ServiceBus.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net6.0;net7.0;net8.0;net9.0
5 | UnitTestEx
6 | UnitTestEx
7 | UnitTestEx Azure Functions Test Extensions.
8 | UnitTestEx Azure ServiceBus Test Extensions.
9 | unittestex azure messaging servicebus unit test unittest
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/src/UnitTestEx.Azure.ServiceBus/strong-name-key.snk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Avanade/UnitTestEx/d80ff2987e1f9aab282aa1c80638ba6015fbff0a/src/UnitTestEx.Azure.ServiceBus/strong-name-key.snk
--------------------------------------------------------------------------------
/src/UnitTestEx.MSTest/Internal/MSTestImplementor.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/UnitTestEx
2 |
3 | using Microsoft.VisualStudio.TestTools.UnitTesting;
4 | using Microsoft.VisualStudio.TestTools.UnitTesting.Logging;
5 |
6 | namespace UnitTestEx.MSTest.Internal
7 | {
8 | ///
9 | /// Provides the MSTest implementation.
10 | ///
11 | public sealed class MSTestImplementor : Abstractions.TestFrameworkImplementor
12 | {
13 | private const string _notSpecifiedText = "Not specified.";
14 |
15 | ///
16 | public override void AssertFail(string? message) => Assert.Fail(message ?? _notSpecifiedText);
17 |
18 | ///
19 | public override void AssertAreEqual(T? expected, T? actual, string? message = null) where T : default => Assert.AreEqual(expected, actual, message, [expected, actual]);
20 |
21 | ///
22 | public override void AssertInconclusive(string? message) => Assert.Inconclusive(message);
23 |
24 | ///
25 | public override void WriteLine(string? message) => Logger.LogMessage("{0}", message);
26 | }
27 | }
--------------------------------------------------------------------------------
/src/UnitTestEx.MSTest/Internal/OneOffTestSetUp.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/UnitTestEx
2 |
3 | using UnitTestEx.Abstractions;
4 |
5 | [assembly: OneOffTestSetUp(typeof(UnitTestEx.MSTest.Internal.OneOffTestSetUp))]
6 |
7 | namespace UnitTestEx.MSTest.Internal
8 | {
9 | ///
10 | /// Performs one-off test set up.
11 | ///
12 | public class OneOffTestSetUp : OneOffTestSetUpBase
13 | {
14 | ///
15 | public override void SetUp() => TestFrameworkImplementor.SetGlobalCreateFactory(() => new MSTestImplementor());
16 | }
17 | }
--------------------------------------------------------------------------------
/src/UnitTestEx.MSTest/UnitTestEx.MSTest.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net6.0;net7.0;net8.0;net9.0
5 | UnitTestEx.MSTest
6 | UnitTestEx MSTest
7 | UnitTestEx MSTest Test Extensions.
8 | UnitTestEx MSTest Test Extensions.
9 | unittestex api function unit test unittest mstest
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/src/UnitTestEx.MSTest/WithApiTester.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/UnitTestEx
2 |
3 | using System;
4 | using UnitTestEx.AspNetCore;
5 |
6 | namespace UnitTestEx
7 | {
8 | ///
9 | /// Provides a shared to enable usage of the same underlying instance across multiple tests.
10 | ///
11 | /// The API startup .
12 | /// Implements to automatically dispose of the instance to release all resources.
13 | /// Be aware that using may result in cross-test contamination.
14 | public abstract class WithApiTester : IDisposable where TEntryPoint : class
15 | {
16 | private bool _disposed;
17 | private ApiTester? _apiTester = ApiTester.Create();
18 |
19 | ///
20 | /// Gets the shared for testing.
21 | ///
22 | public ApiTester Test => _apiTester ?? throw new ObjectDisposedException(nameof(Test));
23 |
24 | ///
25 | /// Dispose of all resources.
26 | ///
27 | /// Indicates whether from the .
28 | protected virtual void Dispose(bool disposing)
29 | {
30 | if (!_disposed)
31 | {
32 | if (disposing)
33 | {
34 | _apiTester?.Dispose();
35 | _apiTester = null;
36 | }
37 |
38 | _disposed = true;
39 | }
40 | }
41 |
42 | ///
43 | public void Dispose()
44 | {
45 | Dispose(disposing: true);
46 | GC.SuppressFinalize(this);
47 | }
48 | }
49 | }
--------------------------------------------------------------------------------
/src/UnitTestEx.MSTest/strong-name-key.snk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Avanade/UnitTestEx/d80ff2987e1f9aab282aa1c80638ba6015fbff0a/src/UnitTestEx.MSTest/strong-name-key.snk
--------------------------------------------------------------------------------
/src/UnitTestEx.NUnit/Internal/NUnitTestImplementor.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/UnitTestEx
2 |
3 | using NUnit.Framework;
4 |
5 | namespace UnitTestEx.NUnit.Internal
6 | {
7 | ///
8 | /// Provides the NUnit implementation.
9 | ///
10 | public sealed class NUnitTestImplementor : Abstractions.TestFrameworkImplementor
11 | {
12 | private const string _notSpecifiedText = "Not specified.";
13 |
14 | ///
15 | public override void AssertFail(string? message) => Assert.Fail(message ?? _notSpecifiedText);
16 |
17 | ///
18 | public override void AssertAreEqual(T? expected, T? actual, string? message = null) where T : default => Assert.That(actual, Is.EqualTo(expected), message);
19 |
20 | ///
21 | public override void AssertInconclusive(string? message) => Assert.Inconclusive(message ?? _notSpecifiedText);
22 |
23 | ///
24 | public override void WriteLine(string? message) => TestContext.Out.WriteLine(message);
25 | }
26 | }
--------------------------------------------------------------------------------
/src/UnitTestEx.NUnit/Internal/OneOffTestSetUp.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/UnitTestEx
2 |
3 | using UnitTestEx.Abstractions;
4 |
5 | [assembly: OneOffTestSetUp(typeof(UnitTestEx.NUnit.Internal.OneOffTestSetUp))]
6 |
7 | namespace UnitTestEx.NUnit.Internal
8 | {
9 | ///
10 | /// Performs one-off test set up.
11 | ///
12 | public class OneOffTestSetUp : OneOffTestSetUpBase
13 | {
14 | ///
15 | public override void SetUp() => TestFrameworkImplementor.SetGlobalCreateFactory(() => new NUnitTestImplementor());
16 | }
17 | }
--------------------------------------------------------------------------------
/src/UnitTestEx.NUnit/UnitTestEx.NUnit.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net6.0;net7.0;net8.0;net9.0
5 | UnitTestEx.NUnit
6 | UnitTestEx NUnit
7 | UnitTestEx NUnit Test Extensions.
8 | UnitTestEx NUnit Test Extensions.
9 | unittestex api function unit test unittest nunit
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/src/UnitTestEx.NUnit/WithApiTester.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/UnitTestEx
2 |
3 | using System;
4 | using UnitTestEx.AspNetCore;
5 |
6 | namespace UnitTestEx
7 | {
8 | ///
9 | /// Provides a shared to enable usage of the same underlying instance across multiple tests.
10 | ///
11 | /// The API startup .
12 | /// Implements to automatically dispose of the instance to release all resources.
13 | /// Be aware that using may result in cross-test contamination.
14 | public abstract class WithApiTester : IDisposable where TEntryPoint : class
15 | {
16 | private bool _disposed;
17 | private ApiTester? _apiTester = ApiTester.Create();
18 |
19 | ///
20 | /// Gets the shared for testing.
21 | ///
22 | public ApiTester Test => _apiTester ?? throw new ObjectDisposedException(nameof(Test));
23 |
24 | ///
25 | /// Dispose of all resources.
26 | ///
27 | /// Indicates whether from the .
28 | protected virtual void Dispose(bool disposing)
29 | {
30 | if (!_disposed)
31 | {
32 | if (disposing)
33 | {
34 | _apiTester?.Dispose();
35 | _apiTester = null;
36 | }
37 |
38 | _disposed = true;
39 | }
40 | }
41 |
42 | ///
43 | public void Dispose()
44 | {
45 | Dispose(disposing: true);
46 | GC.SuppressFinalize(this);
47 | }
48 | }
49 | }
--------------------------------------------------------------------------------
/src/UnitTestEx.NUnit/strong-name-key.snk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Avanade/UnitTestEx/d80ff2987e1f9aab282aa1c80638ba6015fbff0a/src/UnitTestEx.NUnit/strong-name-key.snk
--------------------------------------------------------------------------------
/src/UnitTestEx.Xunit/ApiTestFixture.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/UnitTestEx
2 |
3 | using System;
4 | using UnitTestEx.Abstractions;
5 | using UnitTestEx.AspNetCore;
6 | using UnitTestEx.Xunit.Internal;
7 |
8 | namespace UnitTestEx
9 | {
10 | ///
11 | /// Provides a shared to enable usage of the same underlying instance across multiple tests.
12 | ///
13 | /// Be aware that using may result in cross-test contamination.
14 | public class ApiTestFixture : IDisposable where TEntryPoint : class
15 | {
16 | private ApiTester? _apiTester = ApiTester.Create(() => new XunitLocalTestImplementor());
17 | private bool _disposed;
18 |
19 | ///
20 | /// Initializes a new instance of the class.
21 | ///
22 | public ApiTestFixture()
23 | {
24 | TestFrameworkImplementor.SetLocalCreateFactory(() => new XunitLocalTestImplementor());
25 | OnConfiguration();
26 | }
27 |
28 | ///
29 | /// Provides an opportunity to perform initial configuration before use.
30 | ///
31 | protected virtual void OnConfiguration() { }
32 |
33 | ///
34 | /// Gets the shared for testing.
35 | ///
36 | public ApiTester Test => _apiTester ?? throw new ObjectDisposedException(nameof(Test));
37 |
38 | ///
39 | /// Releases the unmanaged resources and optionally releases the managed resources.
40 | ///
41 | /// true to release both managed and unmanaged resources; false to release only unmanaged resources.
42 | protected virtual void Dispose(bool disposing)
43 | {
44 | if (!_disposed)
45 | {
46 | if (disposing)
47 | {
48 | _apiTester?.Dispose();
49 | _apiTester = null;
50 | }
51 |
52 | _disposed = true;
53 | }
54 | }
55 |
56 | ///
57 | public void Dispose()
58 | {
59 | Dispose(disposing: true);
60 | GC.SuppressFinalize(this);
61 | }
62 | }
63 | }
--------------------------------------------------------------------------------
/src/UnitTestEx.Xunit/Internal/XunitLocalTestImplementor.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/UnitTestEx
2 |
3 | using System.Threading;
4 |
5 | namespace UnitTestEx.Xunit.Internal
6 | {
7 | ///
8 | /// Provides a -based proxy to an instance.
9 | ///
10 | public sealed class XunitLocalTestImplementor() : Abstractions.TestFrameworkImplementor
11 | {
12 | private static readonly AsyncLocal _localImplementor = new();
13 |
14 | ///
15 | /// Sets the .
16 | ///
17 | /// The .
18 | public static void SetLocalImplementor(XunitTestImplementor implementor) => _localImplementor.Value = implementor;
19 |
20 | ///
21 | /// Gets the .
22 | ///
23 | private static XunitTestImplementor? Implementor => _localImplementor.Value;
24 |
25 | ///
26 | public override void AssertAreEqual(T? expected, T? actual, string? message = null) where T : default => Implementor?.AssertAreEqual(expected, actual, message);
27 |
28 | ///
29 | public override void AssertFail(string? message) => Implementor?.AssertFail(message);
30 |
31 | ///
32 | public override void AssertInconclusive(string? message) => Implementor?.AssertInconclusive(message);
33 |
34 | ///
35 | public override void WriteLine(string? message) => Implementor?.WriteLine(message);
36 | }
37 | }
--------------------------------------------------------------------------------
/src/UnitTestEx.Xunit/Internal/XunitTestImplementor.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/UnitTestEx
2 |
3 | using System;
4 | using Xunit;
5 | using Xunit.Abstractions;
6 | using Xunit.Sdk;
7 |
8 | namespace UnitTestEx.Xunit.Internal
9 | {
10 | ///
11 | /// Provides the Xunit implementation.
12 | ///
13 | /// The .
14 | public sealed class XunitTestImplementor(ITestOutputHelper testOutputHelper) : Abstractions.TestFrameworkImplementor
15 | {
16 | private const string _notSpecifiedText = "Not specified.";
17 |
18 | private readonly ITestOutputHelper _output = testOutputHelper ?? throw new ArgumentNullException(nameof(testOutputHelper));
19 |
20 | ///
21 | /// Creates a using the .
22 | ///
23 | /// The .
24 | /// The .
25 | public static XunitTestImplementor Create(ITestOutputHelper testOutputHelper) => new(testOutputHelper);
26 |
27 | ///
28 | public override void AssertFail(string? message) => throw new XunitException(message ?? _notSpecifiedText);
29 |
30 | ///
31 | public override void AssertAreEqual(T? expected, T? actual, string? message = null) where T : default
32 | {
33 | try
34 | {
35 | Assert.Equal(expected, actual);
36 | }
37 | catch (XunitException ex)
38 | {
39 | throw new XunitException(message is null ? ex.Message : $"{message}{Environment.NewLine}{ex.Message}", ex);
40 | }
41 | }
42 |
43 | ///
44 | public override void AssertInconclusive(string? message) => AssertFail($"Inconclusive: {message}");
45 |
46 | ///
47 | public override void WriteLine(string? message) => _output.WriteLine("{0}", message);
48 | }
49 | }
--------------------------------------------------------------------------------
/src/UnitTestEx.Xunit/UnitTestBase.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/UnitTestEx
2 |
3 | using System;
4 | using UnitTestEx.Abstractions;
5 | using UnitTestEx.Xunit.Internal;
6 | using Xunit.Abstractions;
7 |
8 | namespace UnitTestEx
9 | {
10 | ///
11 | /// Provides the base Xunit capabilities.
12 | ///
13 | /// Primarily configures the required for the .
14 | public abstract class UnitTestBase : IDisposable
15 | {
16 | private bool _disposed;
17 |
18 | ///
19 | /// Initializes a new instance of the class.
20 | ///
21 | /// The .
22 | protected UnitTestBase(ITestOutputHelper output)
23 | {
24 | Output = output ?? throw new ArgumentNullException(nameof(output));
25 | TestFrameworkImplementor.SetLocalCreateFactory(() => new XunitTestImplementor(output));
26 | }
27 |
28 | ///
29 | /// Gets the .
30 | ///
31 | protected ITestOutputHelper Output { get; }
32 |
33 | ///
34 | /// Dispose of all resources.
35 | ///
36 | /// Indicates whether from the .
37 | protected virtual void Dispose(bool disposing)
38 | {
39 | if (!_disposed)
40 | {
41 | if (disposing)
42 | TestFrameworkImplementor.ResetLocalCreateFactory();
43 |
44 | _disposed = true;
45 | }
46 | }
47 |
48 | ///
49 | public void Dispose()
50 | {
51 | Dispose(disposing: true);
52 | GC.SuppressFinalize(this);
53 | }
54 | }
55 | }
--------------------------------------------------------------------------------
/src/UnitTestEx.Xunit/UnitTestEx.Xunit.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net6.0;net7.0;net8.0;net9.0
5 | UnitTestEx.Xunit
6 | UnitTestEx Xunit
7 | UnitTestEx Xunit Test Extensions.
8 | UnitTestEx Xunit Test Extensions.
9 | unittestex api function unit test unittest xunit
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/src/UnitTestEx.Xunit/WithApiTester.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/UnitTestEx
2 |
3 | using System;
4 | using UnitTestEx.AspNetCore;
5 | using UnitTestEx.Xunit.Internal;
6 | using Xunit;
7 | using Xunit.Abstractions;
8 |
9 | namespace UnitTestEx
10 | {
11 | ///
12 | /// Provides a shared to enable usage of the same underlying instance across multiple tests.
13 | ///
14 | /// The API startup .
15 | /// Implements to automatically dispose of the instance to release all resources.
16 | /// Be aware that using may result in cross-test contamination.
17 | public abstract class WithApiTester : UnitTestBase, IClassFixture> where TEntryPoint : class
18 | {
19 | ///
20 | /// Initializes a new instance of the class.
21 | ///
22 | /// The shared .
23 | /// The .
24 | public WithApiTester(ApiTestFixture fixture, ITestOutputHelper output) : base(output)
25 | {
26 | Test = fixture.Test;
27 | XunitLocalTestImplementor.SetLocalImplementor(new XunitTestImplementor(output));
28 | }
29 |
30 | ///
31 | /// Gets the shared for testing.
32 | ///
33 | public ApiTester Test { get; }
34 | }
35 | }
--------------------------------------------------------------------------------
/src/UnitTestEx.Xunit/strong-name-key.snk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Avanade/UnitTestEx/d80ff2987e1f9aab282aa1c80638ba6015fbff0a/src/UnitTestEx.Xunit/strong-name-key.snk
--------------------------------------------------------------------------------
/src/UnitTestEx/Abstractions/OneOffTestSetUpAttribute.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/UnitTestEx
2 |
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Reflection;
6 |
7 | namespace UnitTestEx.Abstractions
8 | {
9 | ///
10 | /// Marks a class to be run once before all tests in the assembly.
11 | ///
12 | /// The to invoke.
13 | ///
14 | [AttributeUsage(AttributeTargets.Assembly, Inherited = false, AllowMultiple = false)]
15 | public class OneOffTestSetUpAttribute(Type type) : Attribute
16 | {
17 | private static readonly object _oneOffSetUpLock = new();
18 | private static readonly HashSet _oneOffSetUpTypes = [];
19 |
20 | ///
21 | /// Performs the set up for the specified where configured using the .
22 | ///
23 | /// The .
24 | public static void SetUp(Assembly assembly)
25 | {
26 | var att = assembly.GetCustomAttributes(typeof(OneOffTestSetUpAttribute), false);
27 | if (att.Length > 0 && att[0] is OneOffTestSetUpAttribute sua)
28 | sua.SetUp();
29 | }
30 |
31 | ///
32 | /// Gets the .
33 | ///
34 | public Type Type { get; } = type ?? throw new ArgumentNullException(nameof(type));
35 |
36 | ///
37 | /// Performs the one-off test set up using the specified .
38 | ///
39 | public void SetUp() => ((Activator.CreateInstance(Type) as OneOffTestSetUpBase) ?? throw new InvalidOperationException($"Type {Type.Name} must inherit from {nameof(OneOffTestSetUpBase)}.")).SetUp();
40 | }
41 | }
--------------------------------------------------------------------------------
/src/UnitTestEx/Abstractions/OneOffTestSetUpBase.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/UnitTestEx
2 |
3 | namespace UnitTestEx.Abstractions
4 | {
5 | ///
6 | /// Provides a base class for one-off test set up.
7 | ///
8 | /// The inheriting class must also reference via the to enable.
9 | public abstract class OneOffTestSetUpBase
10 | {
11 | ///
12 | /// Executes the one-off test set up.
13 | ///
14 | public abstract void SetUp();
15 | }
16 | }
--------------------------------------------------------------------------------
/src/UnitTestEx/Abstractions/TestSetUpException.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/UnitTestEx
2 |
3 | using System;
4 |
5 | namespace UnitTestEx.Abstractions
6 | {
7 | ///
8 | /// Represents a exception.
9 | ///
10 | /// The message text.
11 | public class TestSetUpException(string? message = null) : Exception($"The test set up was unsuccessful:{Environment.NewLine}{message ?? "No reason specified."}") { }
12 | }
--------------------------------------------------------------------------------
/src/UnitTestEx/Abstractions/TesterExtensionsConfig.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/UnitTestEx
2 |
3 | using Microsoft.AspNetCore.Mvc;
4 | using Microsoft.Extensions.DependencyInjection;
5 | using System;
6 | using System.Net.Http;
7 | using System.Threading.Tasks;
8 | using UnitTestEx.Expectations;
9 |
10 | namespace UnitTestEx.Abstractions
11 | {
12 | ///
13 | /// Provides the configuration used by the .
14 | ///
15 | public abstract class TesterExtensionsConfig
16 | {
17 | ///
18 | /// Occurs when the is invoked (just prior to invocation).
19 | ///
20 | /// The owning .
21 | public virtual void OnUseSetUp(TesterBase owner) { }
22 |
23 | ///
24 | /// Provides the opportunity to further configure the underlying test host .
25 | ///
26 | /// The owning .
27 | /// The .
28 | public virtual void ConfigureServices(TesterBase owner, IServiceCollection services) { }
29 |
30 | ///
31 | /// Updates the from the (where applicable).
32 | ///
33 | /// The value .
34 | /// The owning .
35 | /// The .
36 | /// The deserialized value (override where applicable).
37 | /// The value will have already been deserialized.
38 | public virtual void UpdateValueFromHttpResponseMessage(TesterBase owner, HttpResponseMessage response, ref TValue? value) { }
39 |
40 | ///
41 | /// Updates the from the (where applicable).
42 | ///
43 | /// The value .
44 | /// The owning .
45 | /// The .
46 | /// The deserialized value (override where applicable).
47 | /// The value will have already been deserialized/converted.
48 | public virtual void UpdateValueFromActionResult(TesterBase owner, IActionResult actionResult, ref TValue? value) { }
49 |
50 | ///
51 | /// Provides the opportunity to extend the functionality.
52 | ///
53 | /// The tester (see ).
54 | /// The being asserted.
55 | /// The .
56 | public virtual Task ExpectationAssertAsync(ExpectationsBase expectation, AssertArgs args) => Task.CompletedTask;
57 | }
58 | }
--------------------------------------------------------------------------------
/src/UnitTestEx/ApiError.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/UnitTestEx
2 |
3 | namespace UnitTestEx
4 | {
5 | ///
6 | /// Represents an API-style error being and .
7 | ///
8 | /// The optional field/property name.
9 | /// The error message.
10 | public class ApiError(string? field, string message)
11 | {
12 | ///
13 | /// Gets the optional field/property name.
14 | ///
15 | public string? Field { get; } = field;
16 |
17 | ///
18 | /// Gets the error message.
19 | ///
20 | public string Message { get; } = message;
21 | }
22 | }
--------------------------------------------------------------------------------
/src/UnitTestEx/ApiTester.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/UnitTestEx
2 |
3 | using System;
4 | using UnitTestEx.Abstractions;
5 | using UnitTestEx.AspNetCore;
6 |
7 | namespace UnitTestEx
8 | {
9 | ///
10 | /// Provides the API testing capability.
11 | ///
12 | public static class ApiTester
13 | {
14 | ///
15 | /// Creates a new instance of the class.
16 | ///
17 | /// The API startup .
18 | /// The optional function to create the instance.
19 | /// The .
20 | public static ApiTester Create(Func? createFactory = null) where TEntryPoint : class => new(createFactory);
21 | }
22 | }
--------------------------------------------------------------------------------
/src/UnitTestEx/AspNetCore/ApiTesterT.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/UnitTestEx
2 |
3 | using System;
4 | using UnitTestEx.Abstractions;
5 |
6 | namespace UnitTestEx.AspNetCore
7 | {
8 | ///
9 | /// Provides the concrete implementation.
10 | ///
11 | /// The API startup .
12 | /// The optional function to create the instance.
13 | public class ApiTester(Func? createFactory = null) : ApiTesterBase>(TestFrameworkImplementor.Create(createFactory)) where TEntryPoint : class { }
14 | }
15 |
--------------------------------------------------------------------------------
/src/UnitTestEx/AspNetCore/BypassAuthorizationHandler.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/UnitTestEx
2 |
3 | using Microsoft.AspNetCore.Authorization;
4 | using System.Linq;
5 | using System.Threading.Tasks;
6 |
7 | namespace UnitTestEx.AspNetCore
8 | {
9 | ///
10 | /// An to bypass authorization checking; i.e. act as anonymous.
11 | ///
12 | public class BypassAuthorizationHandler : IAuthorizationHandler
13 | {
14 | ///
15 | /// Handle by succeeding all checks.
16 | ///
17 | /// The .
18 | public Task HandleAsync(AuthorizationHandlerContext context)
19 | {
20 | foreach (IAuthorizationRequirement requirement in context.PendingRequirements.ToList())
21 | context.Succeed(requirement);
22 |
23 | return Task.CompletedTask;
24 | }
25 | }
26 | }
--------------------------------------------------------------------------------
/src/UnitTestEx/AspNetCore/HttpTesterBaseT.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/UnitTestEx
2 |
3 | using Microsoft.AspNetCore.TestHost;
4 | using System;
5 | using System.Net.Http;
6 | using System.Threading.Tasks;
7 | using UnitTestEx.Abstractions;
8 | using UnitTestEx.Expectations;
9 |
10 | namespace UnitTestEx.AspNetCore
11 | {
12 | ///
13 | /// Provides the base HTTP testing capabilities.
14 | ///
15 | /// The representing itself.
16 | public abstract class HttpTesterBase : HttpTesterBase, IHttpResponseMessageExpectations where TSelf : HttpTesterBase
17 | {
18 | ///
19 | /// Initializes a new class.
20 | ///
21 | /// The owning .
22 | /// The .
23 | public HttpTesterBase(TesterBase owner, TestServer testServer) : base(owner, testServer) => ExpectationsArranger = new ExpectationsArranger(owner, (TSelf)this);
24 |
25 | ///
26 | /// Gets the .
27 | ///
28 | public ExpectationsArranger ExpectationsArranger { get; }
29 |
30 | ///
31 | /// Sets (overrides) the test user name (defaults to ).
32 | ///
33 | /// The test user name.
34 | /// The instance to support fluent-style method-chaining.
35 | public TSelf WithUser(string? userName)
36 | {
37 | UserName = userName;
38 | return (TSelf)this;
39 | }
40 |
41 | ///
42 | /// Sets (overrides) the test user name (defaults to ).
43 | ///
44 | /// The test user identifier.
45 | /// The instance to support fluent-style method-chaining.
46 | /// The is required for the conversion to take place.
47 | public TSelf WithUser(object? userIdentifier)
48 | {
49 | if (userIdentifier == null)
50 | return WithUser(null);
51 |
52 | if (Owner.SetUp.UserNameConverter == null)
53 | throw new InvalidOperationException($"The {nameof(TestSetUp)}.{nameof(TestSetUp.UserNameConverter)} must be defined to support user identifier conversion.");
54 |
55 | return WithUser(Owner.SetUp.UserNameConverter(userIdentifier));
56 | }
57 |
58 | ///
59 | /// Perform the assertion of any expectations.
60 | ///
61 | /// The /
62 | protected override Task AssertExpectationsAsync(HttpResponseMessage res) => ExpectationsArranger.AssertAsync(ExpectationsArranger.CreateArgs(LastLogs).AddExtra(res));
63 | }
64 | }
--------------------------------------------------------------------------------
/src/UnitTestEx/AspNetCore/HttpTesterBaseT2.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/UnitTestEx
2 |
3 | using Microsoft.AspNetCore.TestHost;
4 | using System;
5 | using System.Net.Http;
6 | using System.Threading.Tasks;
7 | using UnitTestEx.Abstractions;
8 | using UnitTestEx.Expectations;
9 |
10 | namespace UnitTestEx.AspNetCore
11 | {
12 | ///
13 | /// Provides the base HTTP testing capabilities.
14 | ///
15 | /// The response value .
16 | /// The representing itself.
17 | public abstract class HttpTesterBase : HttpTesterBase, IHttpResponseMessageExpectations, IValueExpectations where TSelf : HttpTesterBase
18 | {
19 | ///
20 | /// Initializes a new class.
21 | ///
22 | /// The owning .
23 | /// The .
24 | public HttpTesterBase(TesterBase owner, TestServer testServer) : base(owner, testServer) => ExpectationsArranger = new ExpectationsArranger(owner, (TSelf)this);
25 |
26 | ///
27 | /// Gets the .
28 | ///
29 | public ExpectationsArranger ExpectationsArranger { get; }
30 |
31 | ///
32 | /// Sets (overrides) the test user name (defaults to ).
33 | ///
34 | /// The test user name.
35 | /// The instance to support fluent-style method-chaining.
36 | public TSelf WithUser(string? userName)
37 | {
38 | UserName = userName;
39 | return (TSelf)this;
40 | }
41 |
42 | ///
43 | /// Sets (overrides) the test user name (defaults to ).
44 | ///
45 | /// The test user identifier.
46 | /// The instance to support fluent-style method-chaining.
47 | /// The is required for the conversion to take place.
48 | public TSelf WithUser(object? userIdentifier)
49 | {
50 | if (userIdentifier == null)
51 | return WithUser(null);
52 |
53 | if (Owner.SetUp.UserNameConverter == null)
54 | throw new InvalidOperationException($"The {nameof(TestSetUp)}.{nameof(TestSetUp.UserNameConverter)} must be defined to support user identifier conversion.");
55 |
56 | return WithUser(Owner.SetUp.UserNameConverter(userIdentifier));
57 | }
58 |
59 | ///
60 | protected override Task AssertExpectationsAsync(HttpResponseMessage res) => ExpectationsArranger.AssertAsync(ExpectationsArranger.CreateArgs(LastLogs).AddExtra(res));
61 | }
62 | }
--------------------------------------------------------------------------------
/src/UnitTestEx/Assertors/Assertor.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/UnitTestEx
2 |
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Linq;
6 | using System.Text;
7 |
8 | namespace UnitTestEx.Assertors
9 | {
10 | ///
11 | /// Provides assertion helper methods.
12 | ///
13 | public static class Assertor
14 | {
15 | ///
16 | /// Converts the dictionary to an array.
17 | ///
18 | /// The errors.
19 | /// The array.
20 | public static ApiError[] ConvertToApiErrors(IDictionary? errors)
21 | {
22 | List? actual = [];
23 |
24 | if (errors != null)
25 | {
26 | foreach (var kvp in errors.Where(x => !string.IsNullOrEmpty(x.Key)))
27 | {
28 | foreach (var message in kvp.Value.Where(x => !string.IsNullOrEmpty(x)))
29 | {
30 | (actual ??= []).Add(new ApiError(kvp.Key, message));
31 | }
32 | }
33 | }
34 |
35 | return actual?.ToArray() ?? [];
36 | }
37 |
38 | ///
39 | /// Trys to match the and contents.
40 | ///
41 | /// The expected list.
42 | /// The actual list.
43 | /// The error message where they do not match.
44 | /// true where matched; otherwise, false.
45 | public static bool TryAreErrorsMatched(IEnumerable? expected, IEnumerable? actual, out string? errorMessage)
46 | {
47 | var exp = (from e in (expected ?? Array.Empty())
48 | where !(actual ?? Array.Empty()).Any(a => a.Message == e.Message && (e.Field == null || a.Field == e.Field))
49 | select e).ToList();
50 |
51 | var act = (from a in (actual ?? Array.Empty())
52 | where !(expected ?? Array.Empty()).Any(e => a.Message == e.Message && (e.Field == null || a.Field == e.Field))
53 | select a).ToList();
54 |
55 | var sb = new StringBuilder();
56 | if (exp.Count > 0)
57 | {
58 | sb.AppendLine(" Expected messages not matched:");
59 | exp.ForEach(m => sb.AppendLine($" Error: {m.Message} {(m.Field != null ? $"[{m.Field}]" : null)}"));
60 | }
61 |
62 | if (act.Count > 0)
63 | {
64 | sb.AppendLine(" Actual messages not matched:");
65 | act.ForEach(m => sb.AppendLine($" Error: {m.Message} {(m.Field != null ? $"[{m.Field}]" : null)}"));
66 | }
67 |
68 | errorMessage = sb.Length > 0 ? $"Error messages mismatch:{System.Environment.NewLine}{sb}" : null;
69 | return sb.Length == 0;
70 | }
71 | }
72 | }
--------------------------------------------------------------------------------
/src/UnitTestEx/Assertors/AssertorBase.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/UnitTestEx
2 |
3 | using System;
4 | using System.Collections.Generic;
5 | using UnitTestEx.Abstractions;
6 | using UnitTestEx.Json;
7 |
8 | namespace UnitTestEx.Assertors
9 | {
10 | ///
11 | /// Represents the base test assert helper.
12 | ///
13 | /// The owning .
14 | /// The (if any).
15 | public abstract class AssertorBase(TesterBase owner, Exception? exception)
16 | {
17 | private static List>? _assertErrorsExtentions;
18 |
19 | ///
20 | /// Adds an AssertErrors extension to support custom error assertions.
21 | ///
22 | /// The assertion extension.
23 | /// The extension functions return a bool; where true indicates that the function asserted the errors and no further assertions need to be performed.
24 | public static void AddAssertErrorsExtension(Func assertErrorsExtension) => (_assertErrorsExtentions ??= []).Add(assertErrorsExtension);
25 |
26 | ///
27 | /// Gets the owning .
28 | ///
29 | public TesterBase Owner { get; } = owner ?? throw new ArgumentNullException(nameof(owner));
30 |
31 | ///
32 | /// Gets the .
33 | ///
34 | public Exception? Exception { get; } = exception;
35 |
36 | ///
37 | /// Gets the .
38 | ///
39 | protected TestFrameworkImplementor Implementor => Owner.Implementor;
40 |
41 | ///
42 | /// Gets the .
43 | ///
44 | protected IJsonSerializer JsonSerializer => Owner.JsonSerializer;
45 |
46 | ///
47 | /// Asserts the errors using the extensions.
48 | ///
49 | internal void AssertErrorsUsingExtensions(ApiError[] errors)
50 | {
51 | if (_assertErrorsExtentions is null)
52 | throw new NotImplementedException($"AssertErrors is unable to be performed as there are no extensions to perform the assertion; see {nameof(AddAssertErrorsExtension)}.");
53 | else
54 | {
55 | foreach (var ae in _assertErrorsExtentions)
56 | {
57 | if (ae(this, errors))
58 | return;
59 | }
60 |
61 | if (!Assertor.TryAreErrorsMatched(errors, Array.Empty(), out var errorMessage))
62 | Implementor.AssertFail(errorMessage);
63 | }
64 | }
65 | }
66 | }
--------------------------------------------------------------------------------
/src/UnitTestEx/Assertors/HttpResponseMessageAssertorBase.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/UnitTestEx
2 |
3 | using System;
4 | using System.Net.Http;
5 | using System.Net.Mime;
6 | using UnitTestEx.Abstractions;
7 | using UnitTestEx.Json;
8 |
9 | namespace UnitTestEx.Assertors
10 | {
11 | ///
12 | /// Provdes the base test assert helper capabilities.
13 | ///
14 | ///
15 | /// Initializes a new instance of the class.
16 | ///
17 | /// The owning .
18 | /// The .
19 | public abstract class HttpResponseMessageAssertorBase(TesterBase owner, HttpResponseMessage response)
20 | {
21 | ///
22 | /// Gets the owning .
23 | ///
24 | public TesterBase Owner { get; } = owner ?? throw new ArgumentNullException(nameof(owner));
25 |
26 | ///
27 | /// Gets the .
28 | ///
29 | public HttpResponseMessage Response { get; } = response;
30 |
31 | ///
32 | /// Gets the .
33 | ///
34 | protected internal TestFrameworkImplementor Implementor => Owner.Implementor;
35 |
36 | ///
37 | /// Gets the .
38 | ///
39 | public IJsonSerializer JsonSerializer => Owner.JsonSerializer;
40 |
41 | ///
42 | /// Gets the response content as the deserialized JSON value.
43 | ///
44 | /// The content .
45 | /// The expected content type; where null then the content type will not be validated.
46 | /// The result value.
47 | public T? GetValue(string? expectedContentType = MediaTypeNames.Application.Json)
48 | {
49 | if (expectedContentType is not null)
50 | Implementor.AssertAreEqual(expectedContentType, Response.Content?.Headers?.ContentType?.MediaType);
51 |
52 | if (Response.Content == null)
53 | return default!;
54 |
55 | var value = JsonSerializer.Deserialize(Response.Content.ReadAsStringAsync().GetAwaiter().GetResult()!);
56 |
57 | foreach (var ext in TestSetUp.Extensions)
58 | ext.UpdateValueFromHttpResponseMessage(Owner, Response, ref value);
59 |
60 | return value;
61 | }
62 |
63 | ///
64 | /// Gets the response content as a .
65 | ///
66 | /// The result content .
67 | public string? GetContent()
68 | {
69 | if (Response.Content == null)
70 | return null;
71 |
72 | return Response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
73 | }
74 | }
75 | }
--------------------------------------------------------------------------------
/src/UnitTestEx/Assertors/VoidAssertor.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/UnitTestEx
2 |
3 | using System;
4 | using UnitTestEx.Abstractions;
5 |
6 | namespace UnitTestEx.Assertors
7 | {
8 | ///
9 | /// Represents the test assert helper where there is no return value; i.e. .
10 | ///
11 | /// The owning .
12 | /// The (if any).
13 | public class VoidAssertor(TesterBase owner, Exception? exception) : AssertorBase(owner, exception) { }
14 | }
--------------------------------------------------------------------------------
/src/UnitTestEx/Expectations/ExceptionExpectations.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/UnitTestEx
2 |
3 | using System;
4 | using System.Net.Http;
5 | using System.Threading.Tasks;
6 | using UnitTestEx.Abstractions;
7 |
8 | namespace UnitTestEx.Expectations
9 | {
10 | ///
11 | /// Provides expectations.
12 | ///
13 | /// The owning .
14 | /// The initiating tester.
15 | public class ExceptionExpectations(TesterBase owner, TTester tester) : ExpectationsBase(owner, tester)
16 | {
17 | ///
18 | public override int Order => int.MinValue;
19 |
20 | ///
21 | /// Indicates that the expectation is that the execution is successful.
22 | ///
23 | public bool ExpectSuccess { get; set; }
24 |
25 | ///
26 | /// Indicates that the expectation is that an will be thrown during execution.
27 | ///
28 | public bool ExpectException { get; private set; }
29 |
30 | ///
31 | /// Gets the optional .
32 | ///
33 | public Type? ExceptionType { get; private set; }
34 |
35 | ///
36 | /// Gets the optional expected message to match (contains).
37 | ///
38 | public string? ExceptionMessage { get; private set; }
39 |
40 | ///
41 | /// Expects that an will be thrown during execution.
42 | ///
43 | /// The optional .
44 | /// The optional expected message to match (contains).
45 | public void SetExpectException(Type? exceptionType, string? expectedMessage)
46 | {
47 | ExpectException = true;
48 | ExceptionType = exceptionType;
49 | ExceptionMessage = expectedMessage;
50 | }
51 |
52 | ///
53 | protected override Task OnAssertAsync(AssertArgs args)
54 | {
55 | if (ExpectException)
56 | {
57 | if (args.Exception is null)
58 | args.Tester.Implementor.AssertFail("Expected an exception; however, the execution was successful.");
59 | else
60 | {
61 | if (ExceptionType is not null && ExceptionType != args.Exception.GetType())
62 | args.Tester.Implementor.AssertFail($"Expected Exception type '{ExceptionType.Name}' not equal to actual '{args.Exception.GetType().Name}'.");
63 |
64 | if (ExceptionMessage is not null && !args.Exception.Message.Contains(ExceptionMessage, StringComparison.InvariantCultureIgnoreCase))
65 | args.Tester.Implementor.AssertFail($"Expected Exception message '{ExceptionMessage}' is not contained within '{args.Exception.Message}'.");
66 | }
67 | }
68 |
69 | if (ExpectSuccess)
70 | {
71 | if (args.Exception is not null)
72 | args.Tester.Implementor.AssertFail($"Expected success; however, '{args.Exception.GetType().Name}' was thrown: {args.Exception.Message}");
73 |
74 | if (args.TryGetExtra(out var result) && result is not null && !result.IsSuccessStatusCode)
75 | args.Tester.Implementor.AssertFail($"Expected success; however, the {nameof(HttpResponseMessage)} has an unsuccessful {nameof(HttpResponseMessage.StatusCode)} of {result.StatusCode} ({(int)result.StatusCode}).");
76 | }
77 |
78 | return Task.CompletedTask;
79 | }
80 | }
81 | }
--------------------------------------------------------------------------------
/src/UnitTestEx/Expectations/ExpectationsBase.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/UnitTestEx
2 |
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Threading.Tasks;
6 | using UnitTestEx.Abstractions;
7 |
8 | namespace UnitTestEx.Expectations
9 | {
10 | ///
11 | /// Provides the base Expectations implementation.
12 | ///
13 | /// The owning
14 | public abstract class ExpectationsBase(TesterBase owner)
15 | {
16 | private List>>? _extendedActions;
17 |
18 | ///
19 | /// Gets the owning .
20 | ///
21 | public TesterBase Owner { get; } = owner ?? throw new ArgumentNullException(nameof(owner));
22 |
23 | ///
24 | /// Gets or sets the order in which the expectation is asserted.
25 | ///
26 | public virtual int Order { get; } = 100;
27 |
28 | ///
29 | /// Indicates whether to skip the base invocation executing only the extensions.
30 | ///
31 | public bool SkipOnAssert { get; set; }
32 |
33 | ///
34 | /// Adds an extension to the assertion to be executed after the base assertion.
35 | ///
36 | /// The extension.
37 | /// To stop any further extensions being executed, or the , the extension should signal handled with a response of true.
38 | public void AddExtension(Func> extension)
39 | {
40 | _extendedActions ??= [];
41 | _extendedActions.Add(extension ?? throw new ArgumentNullException(nameof(extension)));
42 | }
43 |
44 | ///
45 | /// Performs the base assertion and then any extensions.
46 | ///
47 | /// The .
48 | public async Task AssertAsync(AssertArgs args)
49 | {
50 | ArgumentNullException.ThrowIfNull(args, nameof(args));
51 |
52 | if (!SkipOnAssert)
53 | await OnAssertAsync(args).ConfigureAwait(false);
54 |
55 | if (_extendedActions is not null)
56 | {
57 | foreach (var ea in _extendedActions)
58 | {
59 | if (!await ea(args).ConfigureAwait(false))
60 | return;
61 | }
62 | }
63 |
64 | await OnExtensionsAssert(args).ConfigureAwait(false);
65 | await OnLastAssertAsync(args).ConfigureAwait(false);
66 | }
67 |
68 | ///
69 | /// Performs the assertion.
70 | ///
71 | /// The .
72 | protected abstract Task OnAssertAsync(AssertArgs args);
73 |
74 | ///
75 | /// Performs any extension assertions.
76 | ///
77 | /// The .
78 | protected internal abstract Task OnExtensionsAssert(AssertArgs args);
79 |
80 | ///
81 | /// Performs any final assertion after all extensions have executed.
82 | ///
83 | /// The .
84 | protected virtual Task OnLastAssertAsync(AssertArgs args) => Task.CompletedTask;
85 |
86 | ///
87 | /// Resets the expectation back to its orginating assert state to allow for a re-execution.
88 | ///
89 | public virtual void Reset() { }
90 | }
91 | }
--------------------------------------------------------------------------------
/src/UnitTestEx/Expectations/ExpectationsBaseT.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/UnitTestEx
2 |
3 | using System;
4 | using System.Threading.Tasks;
5 | using UnitTestEx.Abstractions;
6 |
7 | namespace UnitTestEx.Expectations
8 | {
9 | ///
10 | /// Provides the base Expectations implementation.
11 | ///
12 | /// The owning
13 | /// The initiating tester.
14 | public abstract class ExpectationsBase(TesterBase owner, TTester tester) : ExpectationsBase(owner)
15 | {
16 | ///
17 | /// Gets the initiating tester.
18 | ///
19 | public TTester Tester { get; } = tester ?? throw new ArgumentNullException(nameof(tester));
20 |
21 | ///
22 | protected internal override async Task OnExtensionsAssert(AssertArgs args)
23 | {
24 | foreach (var ext in TestSetUp.Extensions)
25 | await ext.ExpectationAssertAsync(this, args).ConfigureAwait(false);
26 | }
27 | }
28 | }
--------------------------------------------------------------------------------
/src/UnitTestEx/Expectations/HttpResponseMessageExpectations.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/UnitTestEx
2 |
3 | using System;
4 | using System.Net;
5 | using System.Net.Http;
6 | using System.Threading.Tasks;
7 | using UnitTestEx.Abstractions;
8 |
9 | namespace UnitTestEx.Expectations
10 | {
11 | ///
12 | /// Provides expectations.
13 | ///
14 | /// The owning .
15 | /// The initiating tester.
16 | public class HttpResponseMessageExpectations(TesterBase owner, TTester tester) : ExpectationsBase(owner, tester)
17 | {
18 | private HttpStatusCode? _httpStatusCode;
19 |
20 | ///
21 | /// Expects that the is equal to the .
22 | ///
23 | /// The .
24 | public void SetExpectStatusCode(HttpStatusCode httpStatusCode) => _httpStatusCode = httpStatusCode;
25 |
26 | ///
27 | protected override Task OnAssertAsync(AssertArgs args)
28 | {
29 | if (!args.TryGetExtra(out var result))
30 | throw new InvalidOperationException($"The '{nameof(HttpResponseMessage)}' Extra value must be set for this expectation assertion to function.");
31 |
32 | if (_httpStatusCode.HasValue && result!.StatusCode != _httpStatusCode)
33 | args.Tester.Implementor.AssertFail($"Expected StatusCode value of '{_httpStatusCode.Value} ({(int)_httpStatusCode.Value})'; actual was '{result.StatusCode} ({(int)result.StatusCode})'.");
34 |
35 | return Task.CompletedTask;
36 | }
37 | }
38 | }
--------------------------------------------------------------------------------
/src/UnitTestEx/Expectations/IExpectations.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/UnitTestEx
2 |
3 | namespace UnitTestEx.Expectations
4 | {
5 | ///
6 | /// Enables the Expectations fluent-style method-chaining capabilities.
7 | ///
8 | /// The representing itself.
9 | public interface IExpectations where TSelf : IExpectations
10 | {
11 | ///
12 | /// Gets the .
13 | ///
14 | ExpectationsArranger ExpectationsArranger { get; }
15 | }
16 | }
--------------------------------------------------------------------------------
/src/UnitTestEx/Expectations/IHttpResponseMessageExpectations.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/UnitTestEx
2 |
3 | using System.Net.Http;
4 |
5 | namespace UnitTestEx.Expectations
6 | {
7 | ///
8 | /// Enables the Expectations fluent-style method-chaining capabilities.
9 | ///
10 | /// The representing itself.
11 | public interface IHttpResponseMessageExpectations : IExpectations where TSelf : IHttpResponseMessageExpectations { }
12 | }
--------------------------------------------------------------------------------
/src/UnitTestEx/Expectations/IValueExpectations.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/UnitTestEx
2 |
3 | namespace UnitTestEx.Expectations
4 | {
5 | ///
6 | /// Enables the Expectations fluent-style method-chaining capabilities.
7 | ///
8 | /// The response value .
9 | /// The representing itself.
10 | public interface IValueExpectations : IExpectations where TSelf : IValueExpectations { }
11 | }
--------------------------------------------------------------------------------
/src/UnitTestEx/Expectations/LoggerExpectations.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/UnitTestEx
2 |
3 | using Microsoft.Extensions.Logging;
4 | using System;
5 | using System.Collections.Generic;
6 | using System.Linq;
7 | using System.Threading.Tasks;
8 | using UnitTestEx.Abstractions;
9 |
10 | namespace UnitTestEx.Expectations
11 | {
12 | ///
13 | /// Provides log expectations.
14 | ///
15 | /// The owning .
16 | /// The initiating tester.
17 | public class LoggerExpectations(TesterBase owner, TTester tester) : ExpectationsBase(owner, tester)
18 | {
19 | private readonly List _expectTexts = [];
20 |
21 | ///
22 | /// Expects that the will have logged a message that contains the specified .
23 | ///
24 | /// The text(s) that should appear in at least one log message line.
25 | public void SetExpectLogContains(params string[] texts) => _expectTexts.AddRange(texts.Where(t => !string.IsNullOrEmpty(t)));
26 |
27 | ///
28 | protected override Task OnAssertAsync(AssertArgs args)
29 | {
30 | if ((args.Logs is null || !args.Logs.Any()) && _expectTexts.Count > 0)
31 | args.Tester.Implementor.AssertFail($"Expected one or more log texts that were not found.");
32 |
33 | foreach (var et in _expectTexts)
34 | {
35 | if (args.Logs!.Any(l => !string.IsNullOrEmpty(l) && l.Contains(et, StringComparison.InvariantCultureIgnoreCase)))
36 | continue;
37 |
38 | args.Tester.Implementor.AssertFail($"Expected a log text to contain '{et}' that was not found.");
39 | }
40 |
41 | return Task.CompletedTask;
42 | }
43 | }
44 | }
--------------------------------------------------------------------------------
/src/UnitTestEx/Expectations/ValueExpectations.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/UnitTestEx
2 |
3 | using System;
4 | using System.Linq;
5 | using System.Net.Http;
6 | using System.Threading.Tasks;
7 | using UnitTestEx.Abstractions;
8 | using UnitTestEx.Json;
9 |
10 | namespace UnitTestEx.Expectations
11 | {
12 | ///
13 | /// Provides value expectations.
14 | ///
15 | /// The owning .
16 | /// The initiating tester.
17 | public class ValueExpectations(TesterBase owner, TTester tester) : ExpectationsBase(owner, tester)
18 | {
19 | private const string NullJson = "null";
20 | private Func? _json;
21 | private bool _expectNull;
22 |
23 | ///
24 | /// Expects that the result JSON compares to the expected .
25 | ///
26 | /// The expected JSON function.
27 | public void SetExpectJson(Func json) => _json = json ?? throw new ArgumentNullException(nameof(json));
28 |
29 | ///
30 | /// Expects the the result JSON is null.
31 | ///
32 | public void SetExpectNull() => _expectNull = true;
33 |
34 | ///
35 | /// Indicates whether the expected value was matched.
36 | ///
37 | /// This must be set to true once matched; otherwise, the will assert a failure.
38 | public bool ValueMatched { get; private set; }
39 |
40 | ///
41 | protected async override Task OnAssertAsync(AssertArgs args)
42 | {
43 | if (_json is null)
44 | return; // No value configured to compare.
45 |
46 | JsonElementComparerResult? jcr;
47 | var jc = new JsonElementComparer(new JsonElementComparerOptions { JsonSerializer = args.Tester.JsonSerializer });
48 |
49 | string expectedJson = _expectNull ? NullJson : (_json?.Invoke(Tester) ?? NullJson);
50 |
51 | // Where there is an explicit value, serialize and compare.
52 | if (args.HasValue)
53 | jcr = jc.Compare(expectedJson, args.Tester.JsonSerializer.Serialize(args.HasValue ? args.Value : null), args.PathsToIgnore.ToArray());
54 | else
55 | {
56 | // Where there is no explicit value, see if there is an HTTP response.
57 | if (!args.TryGetExtra(out var result) || result is null)
58 | return;
59 |
60 | if (!result.IsSuccessStatusCode)
61 | {
62 | args.Tester.Implementor.AssertFail($"Expected value; however, the {nameof(HttpResponseMessage)} has an unsuccessful {nameof(HttpResponseMessage.StatusCode)} of {result.StatusCode} ({(int)result.StatusCode}).");
63 | return;
64 | }
65 |
66 | jcr = jc.Compare(expectedJson, result.Content.Headers.ContentLength == 0 ? NullJson : await result.Content.ReadAsStringAsync().ConfigureAwait(false), args.PathsToIgnore.ToArray());
67 | }
68 |
69 | // Assert the differences.
70 | if (jcr.HasDifferences)
71 | args.Tester.Implementor.AssertFail($"Expected and Actual values are not equal:{Environment.NewLine}{jcr}");
72 | else
73 | ValueMatched = true;
74 | }
75 |
76 | ///
77 | protected override Task OnLastAssertAsync(AssertArgs args)
78 | {
79 | if (!ValueMatched)
80 | args.Tester.Implementor.AssertFail($"Expected value; however, none was returned.");
81 |
82 | return Task.CompletedTask;
83 | }
84 |
85 | ///
86 | public override void Reset()
87 | {
88 | base.Reset();
89 | ValueMatched = false;
90 | }
91 | }
92 | }
--------------------------------------------------------------------------------
/src/UnitTestEx/Generic/GenericTesterT.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/UnitTestEx
2 |
3 | using UnitTestEx.Abstractions;
4 |
5 | namespace UnitTestEx.Generic
6 | {
7 | ///
8 | /// Provides the NUnit generic testing capability.
9 | ///
10 | public class GenericTester() : GenericTesterBase>(TestFrameworkImplementor.Create()) where TEntryPoint : class { }
11 | }
--------------------------------------------------------------------------------
/src/UnitTestEx/Generic/GenericTesterT2.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/UnitTestEx
2 |
3 | using UnitTestEx.Abstractions;
4 |
5 | namespace UnitTestEx.Generic
6 | {
7 | ///
8 | /// Provides the NUnit generic testing capability.
9 | ///
10 | public class GenericTester() : GenericTesterBase>(TestFrameworkImplementor.Create()) where TEntryPoint : class { }
11 | }
--------------------------------------------------------------------------------
/src/UnitTestEx/GenericTester.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/UnitTestEx
2 |
3 | using System;
4 | using UnitTestEx.Generic;
5 | using UnitTestEx.Hosting;
6 |
7 | namespace UnitTestEx
8 | {
9 | ///
10 | /// Provides the NUnit generic testing capability.
11 | ///
12 | public static class GenericTester
13 | {
14 | ///
15 | /// Creates a new instance of the class.
16 | ///
17 | /// The .
18 | public static GenericTester