diff --git a/.github/workflows/dotnetCi.yml b/.github/workflows/dotnetCi.yml index ea191c6d..da1a16f3 100644 --- a/.github/workflows/dotnetCi.yml +++ b/.github/workflows/dotnetCi.yml @@ -41,152 +41,149 @@ jobs: DISPLAY: :99 steps: - - name: Checkout - uses: actions/checkout@v4 - - # Install build dependencies - - - name: Add .NET global tools location to PATH - run: echo "$HOME/.dotnet/tools" >> "$GITHUB_PATH" - - name: Install .NET - uses: actions/setup-dotnet@v4 - with: - dotnet-version: ${{ env.DotnetVersion }} - - name: Install SonarScanner - run: dotnet tool install --global dotnet-sonarscanner - - name: Install Coverlet console - run: dotnet tool install --global coverlet.console - - name: Install DocFX - run: dotnet tool install --global docfx - - name: Install Node.js for building JSON-to-HTML report converter - uses: actions/setup-node@v6.2.0 - - name: Install Java JDK for SonarScanner - uses: actions/setup-java@v5.1.0 - with: - java-version: 21 - distribution: 'zulu' - - name: Install GUI packages so Chrome may run - run: | - sudo apt-get update - sudo apt install -y xorg xvfb gtk2-engines-pixbuf dbus-x11 xfonts-base xfonts-100dpi xfonts-75dpi xfonts-cyrillic xfonts-scalable psmisc - - # Environment setup pre-build - - - name: Setup Selenium Manager config - run: | - mkdir ~/.cache/selenium - cp Tools/se-config.toml ~/.cache/selenium - - name: Start an Xvfb display so Chrome may run - run: Xvfb -ac $DISPLAY -screen 0 1280x1024x16 & - - name: Restore .NET packages - run: dotnet restore - - name: Restore Node modules - run: | - cd CSF.Screenplay.JsonToHtmlReport.Template/src - npm ci - cd ../.. - - # Build and test the solution - - - name: Start SonarScanner - run: > - dotnet sonarscanner begin - /k:${{ env.SonarCloudProject }} - /v:GitHub_build_${{ env.RunNumber }} - /o:${{ env.SonarCloudUsername }} - /d:sonar.host.url=${{ env.SonarCloudUrl }} - /d:sonar.token=${{ env.SonarCloudSecretKey }} - /d:${{ env.BranchParam }}=${{ env.BranchName }} ${{ env.PullRequestParam }} - /d:sonar.javascript.lcov.reportPaths=CSF.Screenplay.JsonToHtmlReport.Template/src/TestResults/lcov.info - /s:$PWD/.sonarqube-analysisproperties.xml - - name: Build the solution - run: dotnet build -c ${{ env.Configuration }} --no-incremental - - name: Run .NET tests with coverage - id: dotnet_tests - continue-on-error: true - run: | - for proj in Tests/*.Tests - do - projNameArray=(${proj//// }) - projName=${projNameArray[1]} - assemblyPath=$proj/bin/$Configuration/$Tfm/$projName.dll - coverlet "$assemblyPath" --target "dotnet" --targetargs "test $proj -c $Configuration --no-build --logger:nunit --test-adapter-path:." -f=opencover -o="TestResults/$projName.opencover.xml" + - name: Checkout + uses: actions/checkout@v4 + + # Install build dependencies + + - name: Add .NET global tools location to PATH + run: echo "$HOME/.dotnet/tools" >> "$GITHUB_PATH" + - name: Install .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: ${{ env.DotnetVersion }} + - name: Install SonarScanner + run: dotnet tool install --global dotnet-sonarscanner + - name: Install Coverlet console + run: dotnet tool install --global coverlet.console + - name: Install DocFX + run: dotnet tool install --global docfx + - name: Install Node.js for building JSON-to-HTML report converter + uses: actions/setup-node@v6.2.0 + - name: Install Java JDK for SonarScanner + uses: actions/setup-java@v5.1.0 + with: + java-version: 21 + distribution: 'zulu' + - name: Install GUI packages so Chrome may run + run: | + sudo apt-get update + sudo apt install -y xorg xvfb gtk2-engines-pixbuf dbus-x11 xfonts-base xfonts-100dpi xfonts-75dpi xfonts-cyrillic xfonts-scalable psmisc + + # Environment setup pre-build + + - name: Setup Selenium Manager config + run: | + mkdir ~/.cache/selenium + cp Tools/se-config.toml ~/.cache/selenium + - name: Start an Xvfb display so Chrome may run + run: Xvfb -ac $DISPLAY -screen 0 1280x1024x16 & + - name: Restore .NET packages + run: dotnet restore + - name: Restore Node modules + run: | + cd CSF.Screenplay.JsonToHtmlReport.Template/src + npm ci + cd ../.. + + # Build and test the solution + + - name: Start SonarScanner + run: > + dotnet sonarscanner begin + /k:${{ env.SonarCloudProject }} + /v:GitHub_build_${{ env.RunNumber }} + /o:${{ env.SonarCloudUsername }} + /d:sonar.host.url=${{ env.SonarCloudUrl }} + /d:sonar.token=${{ env.SonarCloudSecretKey }} + /d:${{ env.BranchParam }}=${{ env.BranchName }} ${{ env.PullRequestParam }} + /d:sonar.javascript.lcov.reportPaths=CSF.Screenplay.JsonToHtmlReport.Template/src/TestResults/lcov.info + /s:$PWD/.sonarqube-analysisproperties.xml + - name: Build the solution + run: dotnet build -c ${{ env.Configuration }} --no-incremental + - name: Run .NET tests with coverage + id: dotnet_tests + continue-on-error: true + run: | + for proj in Tests/*.Tests + do + projNameArray=(${proj//// }) + projName=${projNameArray[1]} + assemblyPath=$proj/bin/$Configuration/$Tfm/$projName.dll + coverlet "$assemblyPath" --target "dotnet" --targetargs "test $proj -c $Configuration --no-build --logger:nunit --test-adapter-path:." -f=opencover -o="TestResults/$projName.opencover.xml" + if [ $? -ne 0 ] + then + echo "failures=true" >> $GITHUB_OUTPUT + fi + done + - name: Run JavaScript tests with coverage + id: js_tests + continue-on-error: true + run: | + cd CSF.Screenplay.JsonToHtmlReport.Template/src + npm test if [ $? -ne 0 ] then echo "failures=true" >> $GITHUB_OUTPUT fi - done - - name: Run JavaScript tests with coverage - id: js_tests - continue-on-error: true - run: | - cd CSF.Screenplay.JsonToHtmlReport.Template/src - npm test - if [ $? -ne 0 ] - then - echo "failures=true" >> $GITHUB_OUTPUT - fi - cd ../.. - - # Post-test tasks (artifacts, overall status) - - - name: Stop SonarScanner - run: - dotnet sonarscanner end /d:sonar.token=${{ env.SonarCloudSecretKey }} - - name: Gracefully stop Xvfb - run: killall Xvfb - continue-on-error: true - - name: Upload test results artifacts - uses: actions/upload-artifact@v4 - with: - name: NUnit test results - path: Tests/*.Tests/**/TestResults.xml - - name: Upload Screenplay JSON report artifact - uses: actions/upload-artifact@v4 - with: - name: Screenplay JSON reports - path: Tests/**/ScreenplayReport_*.json - - name: Convert Screenplay reports to HTML - continue-on-error: true - run: | - for report in $(find Tests/ -type f -name "ScreenplayReport_*.json") - do - reportDir=$(dirname "$report") - outputFile="$reportDir/ScreenplayReport.html" - dotnet run --no-build --framework $Tfm --project CSF.Screenplay.JsonToHtmlReport --ReportPath "$report" --OutputPath "$outputFile" - done - - name: Upload Screenplay HTML report artifact - uses: actions/upload-artifact@v4 - with: - name: Screenplay HTML reports - path: Tests/**/ScreenplayReport.html - - name: Fail the build if any test failures - if: ${{ steps.dotnet_tests.outputs.failures == 'true' || steps.js_tests.outputs.failures == 'true' }} - run: | - echo "Failing the build due to test failures" - exit 1 - - # Build the apps in release mode and publish artifacts - - - name: Clean the solution ahead of building in release config - run: dotnet clean - - name: Build, in release configuration - run: dotnet pack -p:VersionSuffix=$VersionSuffix -o packages - - name: Upload build result artifacts - uses: actions/upload-artifact@v4 - with: - name: Build results (NuGet) - path: packages/*.nupkg - - name: Build docs website - run: dotnet build -c Docs - - name: Upload docs website artifact - uses: actions/upload-artifact@v4 - with: - name: Docs website - path: docs/**/* - - # publishDocs: - # TODO: Take the docco site artifact and publish it to master + cd ../.. + + # Post-test tasks (artifacts, overall status) + + - name: Stop SonarScanner + run: + dotnet sonarscanner end /d:sonar.token=${{ env.SonarCloudSecretKey }} + - name: Gracefully stop Xvfb + run: killall Xvfb + continue-on-error: true + - name: Upload test results artifacts + uses: actions/upload-artifact@v4 + with: + name: NUnit test results + path: Tests/*.Tests/**/TestResults.xml + - name: Upload Screenplay JSON report artifact + uses: actions/upload-artifact@v4 + with: + name: Screenplay JSON reports + path: Tests/**/ScreenplayReport_*.json + - name: Convert Screenplay reports to HTML + continue-on-error: true + run: | + for report in $(find Tests/ -type f -name "ScreenplayReport_*.json") + do + reportDir=$(dirname "$report") + outputFile="$reportDir/ScreenplayReport.html" + dotnet run --no-build --framework $Tfm --project CSF.Screenplay.JsonToHtmlReport --ReportPath "$report" --OutputPath "$outputFile" + done + - name: Upload Screenplay HTML report artifact + uses: actions/upload-artifact@v4 + with: + name: Screenplay HTML reports + path: Tests/**/ScreenplayReport.html + - name: Fail the build if any test failures + if: ${{ steps.dotnet_tests.outputs.failures == 'true' || steps.js_tests.outputs.failures == 'true' }} + run: | + echo "Failing the build due to test failures" + exit 1 + + # Build the apps in release mode and publish artifacts + + - name: Clean the solution ahead of building in release config + run: dotnet clean + - name: Build, in release configuration + run: dotnet pack -p:VersionSuffix=$VersionSuffix -o packages + - name: Upload build result artifacts + uses: actions/upload-artifact@v4 + with: + name: Build results (NuGet) + path: packages/*.nupkg + - name: Build docs website + run: dotnet build -c Docs + - name: Upload docs website artifact + uses: actions/upload-artifact@v4 + with: + name: Docs website + path: docs/**/* # runBrowserTests: # TODO: Use build-results artifacts and run tests on matrix of browsers diff --git a/.github/workflows/supportVerification.yml b/.github/workflows/supportVerification.yml new file mode 100644 index 00000000..f09aacbe --- /dev/null +++ b/.github/workflows/supportVerification.yml @@ -0,0 +1,38 @@ +name: Third party support verification + +on: + schedule: + - cron: "25 19 * * 0" + +jobs: + + # Summary: + # + # Runs a subset of the unit tests for Reqnroll, across a matrix + # of versions of this 3rd party library. + # This verifies that I support the version range which I claim to support. + # + # If this begins failing then it likely means that Reqnroll have released + # a version which includes breaking changes, and so the Screenplay support + # policy/version range must be updated accordingly. + + test_reqnroll_versions: + name: Verify Reqnroll support across versions + runs-on: ubuntu-slim + + strategy: + matrix: + reqnroll_version: ['2.0.0', '2.*', '3.0.0', '3.*', '*'] + + env: + DotnetVersion: 8.0.x + + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Install .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: ${{ env.DotnetVersion }} + - name: Test Reqnroll (only) + run: dotnet test Tests/CSF.Screenplay.Reqnroll.Tests -p:ReqnrollVersion=${{ matrix.reqnroll_version }} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index c32efd83..c5fd275a 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,7 @@ { - "cucumberautocomplete.steps": [ "Tests/CSF.Screenplay.SpecFlow.Tests/StepDefinitions/**/*.cs" ], + "cucumberautocomplete.steps": [ + "Tests/CSF.Screenplay.SpecFlow.Tests/StepDefinitions/**/*.cs", + "Tests/CSF.Screenplay.Reqnroll.Tests/StepDefinitions/**/*.cs" + ], "cucumberautocomplete.strictGherkinCompletion": true, } \ No newline at end of file diff --git a/CSF.Screenplay.Docs/docs/bestPractice/SuitabilityAsATestingTool.md b/CSF.Screenplay.Docs/docs/bestPractice/SuitabilityAsATestingTool.md index 465f930d..cfde37b6 100644 --- a/CSF.Screenplay.Docs/docs/bestPractice/SuitabilityAsATestingTool.md +++ b/CSF.Screenplay.Docs/docs/bestPractice/SuitabilityAsATestingTool.md @@ -1,7 +1,7 @@ # Where Screenplay is suitable for testing The Screenplay pattern is a recommended tool for writing automated tests for application software. -Unlike [NUnit] or [SpecFlow] (or many others) Screenplay is not a complete testing framework. +Unlike [NUnit] or [Reqnroll] (or many others) Screenplay is not a complete testing framework. Rather, Screenplay integrates with your chosen testing framework to assist in the writing of test logic. _Screenplay is not a silver bullet_; some kinds of tests could benefit from Screenplay and others will not. @@ -9,7 +9,7 @@ Some testing scenarios are listed below, along with a brief consideration as to Terminology can differ between developers, so each type of test begins with a short definition. [NUnit]: https://nunit.org/ -[SpecFlow]: https://specflow.org/ +[Reqnroll]: https://reqnroll.net/ ## Ideal: System tests @@ -65,6 +65,6 @@ This is particularly true if the test/sample scenarios would be recognisable to ## Recommended: Use BDD-style tests Screenplay is a great tool when used alongside [Behaviour Driven Development] (BDD). -Whilst the use of a BDD framework such as [SpecFlow] is not at all mandatory, those familiar with BDD will quickly see the synergies with Screenplay. +Whilst the use of a BDD framework such as [Reqnroll] is not at all mandatory, those familiar with BDD will quickly see the synergies with Screenplay. [Behaviour Driven Development]: https://en.wikipedia.org/wiki/Behavior-driven_development diff --git a/CSF.Screenplay.Docs/docs/bestPractice/WritingTests.md b/CSF.Screenplay.Docs/docs/bestPractice/WritingTests.md index 2b3f7fdc..022f98a8 100644 --- a/CSF.Screenplay.Docs/docs/bestPractice/WritingTests.md +++ b/CSF.Screenplay.Docs/docs/bestPractice/WritingTests.md @@ -11,7 +11,7 @@ Rather than using many performables at the top-level of your tests, create [high When using a method-driven testing framework, such as NUnit, five performables in a single test method is a reasonable number. More than approximately ten performables is too many. -When using a binding-driven testing framework like SpecFlow, each binding should ideally correspond to at-most one performable. +When using a binding-driven testing framework like Reqnroll, each binding should ideally correspond to at-most one performable. [performables]: ../../glossary/Performable.md [high-level tasks]: ../../glossary/Task.md diff --git a/CSF.Screenplay.Docs/docs/dependencyInjection/InjectingServices.md b/CSF.Screenplay.Docs/docs/dependencyInjection/InjectingServices.md index 00e917a1..dc46d47a 100644 --- a/CSF.Screenplay.Docs/docs/dependencyInjection/InjectingServices.md +++ b/CSF.Screenplay.Docs/docs/dependencyInjection/InjectingServices.md @@ -17,13 +17,13 @@ Which of these depends upon the nature and paradigm of the test framework. For frameworks which are based on **test methods** such as [NUnit], services are typically injected via _method parameter injection_ into the test methods. [If Screenplay were to be extended] to work with frameworks such as xUnit or MSTest then this is likely to be the technique used. -For frameworks which are based on **binding classes** such as [SpecFlow], services are constructor-injected into binding classes. +For frameworks which are based on **binding classes** such as [Reqnroll], services are constructor-injected into binding classes. Use dependencies injected in this way to get access to [commonly-used Screenplay services] and anything else required at the root level of your test logic. [NUnit]: https://nunit.org/ [If Screenplay were to be extended]: ../extendingScreenplay/TestIntegrations.md -[SpecFlow]: https://specflow.org/ +[Reqnroll]: https://reqnroll.net/ [commonly-used Screenplay services]: InjectableServices.md ## Into standalone performance logic diff --git a/CSF.Screenplay.Docs/docs/extendingScreenplay/TestIntegrations.md b/CSF.Screenplay.Docs/docs/extendingScreenplay/TestIntegrations.md index 14a3d0db..c04f84d0 100644 --- a/CSF.Screenplay.Docs/docs/extendingScreenplay/TestIntegrations.md +++ b/CSF.Screenplay.Docs/docs/extendingScreenplay/TestIntegrations.md @@ -1,11 +1,11 @@ # Writing new Test framework integrations A way in which Screenplay is available for extension is the integration of Screenplay with other Test frameworks. -Screenplay currently ships with integrations for [NUnit] and [SpecFlow] but developers are free to integrate it into other frameworks if they wish. +Screenplay currently ships with integrations for [NUnit] and [Reqnroll] but developers are free to integrate it into other frameworks if they wish. Developers who are interested in this may use the source code to these two integrations as inspiration. [NUnit]: https://nunit.org -[SpecFlow]: https://specflow.org +[Reqnroll]: https://reqnroll.net/ ## Requirements @@ -33,7 +33,7 @@ Terminologies differ between testing frameworks; the word [Scenario] is used to * Failure to do this could lead to memory leaks or unnecesarily high resource usage whilst the [Screenplay] is in-progress * The test framework **must** provide access to at least the [`ICast`] and [`IStage`], resolved from the [Scenario]'s dependency injection scope, to the [Scenario] logic * The manner of doing this depends entirely on the test framework - * By way of example, in NUnit this is performed by providing the values of parameters to the test method, in SpecFlow this is performed by resolving step bindng classes from that same DI scope, allowing constructor injection + * By way of example, in NUnit this is performed by providing the values of parameters to the test method, in Reqnroll this is performed by resolving step bindng classes from that same DI scope, allowing constructor injection [`Screenplay`]: xref:CSF.Screenplay.Screenplay [Scenarios]: ../../glossary/Scenario.md diff --git a/CSF.Screenplay.Docs/docs/gettingStarted/reqnroll/index.md b/CSF.Screenplay.Docs/docs/gettingStarted/reqnroll/index.md index 6d2b3a3a..dd5e780d 100644 --- a/CSF.Screenplay.Docs/docs/gettingStarted/reqnroll/index.md +++ b/CSF.Screenplay.Docs/docs/gettingStarted/reqnroll/index.md @@ -1,25 +1,33 @@ -# Screenplay & SpecFlow tutorial +# Screenplay & Reqnroll tutorial -Begin writing SpecFlow tests using Screenplay by following these steps. +> [!TIP] +> Are you using the legacy **SpecFlow**? +> **Reqnroll** is the maintained fork of SpecFlow, so it's recommended you upgrade your projects ASAP. +> +> For now, CSF.Screenplay continues to support SpecFlow. +> Use the [CSF.Screenplay.SpecFlow] package and SpecFlow v3.4.3 or higher instead. +> The remainder of the instructions below work for either Reqnroll or SpecFlow. + +Begin writing Reqnroll tests using Screenplay by following these steps. Further detail is provided below. -1. Ensure that your test project uses [SpecFlow version 3.4.3] or higher -1. Install the NuGet package **[CSF.Screenplay.SpecFlow]** to the project which will contain your `.feature` files -1. _Optional:_ Add services to dependency injection which will be required by the [Abilities] you intend to use. If required, [use SpecFlow context injection & hooks] to add these to the DI container. +1. Ensure that your test project uses [Reqnroll version 2.0.0] or higher +1. Install the NuGet package **[CSF.Screenplay.Reqnroll]** to the project which will contain your `.feature` files +1. _Optional:_ Add services to dependency injection which will be required by the [Abilities] you intend to use. If required, [use Reqnroll context injection & hooks] to add these to the DI container. 1. Write step binding classes which dependency-inject and use Screenplay's architecture -[SpecFlow version 3.4.3]: https://www.nuget.org/packages/SpecFlow/3.4.3 -[CSF.Screenplay.SpecFlow]: https://www.nuget.org/packages/CSF.Screenplay.SpecFlow +[Reqnroll version 2.0.0]: https://www.nuget.org/packages/Reqnroll/2.0.0 +[CSF.Screenplay.Reqnroll]: https://www.nuget.org/packages/CSF.Screenplay.Reqnroll [Abilities]: ../../../glossary/Ability.md -[use SpecFlow context injection & hooks]: https://docs.specflow.org/projects/specflow/en/latest/Bindings/Context-Injection.html#advanced-options +[use Reqnroll context injection & hooks]:https://docs.reqnroll.net/latest/automation/context-injection.html#advanced-options ## Writing step bindings > [!IMPORTANT] -> When using SpecFlow with Screenplay, every Screenplay-using test within a test assembly (thus, within a .NET project) must share the same instance of `Screenplay`. +> When using Reqnroll with Screenplay, every Screenplay-using test within a test assembly (thus, within a .NET project) must share the same instance of `Screenplay`. > This is not expected to be problematic, as all the [`Screenplay`] object does is set-up the Screenplay architecture and dependency injection for the tests. -When using Screenplay with SpecFlow, `.feature` files are written as normal. +When using Screenplay with Reqnroll, `.feature` files are written as normal. The only difference in writing your tests is that **Step Binding** classes should inject Screenplay architecture and use it within the bindings. The recommended services to inject into your step binding classes are either [`IStage`] or [`ICast`]. @@ -38,7 +46,7 @@ If you are not using Personas to get actors, then you might also need to inject > The implied ability, the performables, persona and `DishwashingBuilder` used in this test, related to washing dishes, are all fictitious. > See [the documentation for writing performables] to learn about how these could be written. -This example assumes that SpecFlow is writting using [the NUnit runner], and thus [it makes use of NUnit-style assertions]. +This example assumes that Reqnroll is writting using [the NUnit runner], and thus [it makes use of NUnit-style assertions]. Feel free to replace the assertion _with whichever assertion library you wish to use_. ```csharp @@ -75,5 +83,5 @@ public class WashTheDishesSteps(IStage stage) ``` [the documentation for writing performables]: ../../writingPerformables/index.md -[the NUnit runner]: https://docs.specflow.org/projects/specflow/en/latest/Installation/Unit-Test-Providers.html +[the NUnit runner]: https://docs.reqnroll.net/latest/integrations/nunit.html [it makes use of NUnit-style assertions]: https://docs.nunit.org/articles/nunit/writing-tests/assertions/assertion-models/constraint.html diff --git a/CSF.Screenplay.Reqnroll/CSF.Screenplay.Reqnroll.csproj b/CSF.Screenplay.Reqnroll/CSF.Screenplay.Reqnroll.csproj new file mode 100644 index 00000000..a4def43a --- /dev/null +++ b/CSF.Screenplay.Reqnroll/CSF.Screenplay.Reqnroll.csproj @@ -0,0 +1,26 @@ + + + + + + + + CSF.Screenplay + $(MSBuildProjectDirectory)\bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml + false + false + CSF.Screenplay.ReqnrollPlugin + A Test Framework Integration for CSF.Screenplay for Reqnroll (the maintained fork of SpecFlow) + + + + + + + + + + + + + diff --git a/CSF.Screenplay.SpecFlow/Properties/RuntimePluginInfo.cs b/CSF.Screenplay.Reqnroll/Properties/RuntimePluginInfo.cs similarity index 68% rename from CSF.Screenplay.SpecFlow/Properties/RuntimePluginInfo.cs rename to CSF.Screenplay.Reqnroll/Properties/RuntimePluginInfo.cs index 2c54f8f7..9f038339 100644 --- a/CSF.Screenplay.SpecFlow/Properties/RuntimePluginInfo.cs +++ b/CSF.Screenplay.Reqnroll/Properties/RuntimePluginInfo.cs @@ -1,4 +1,8 @@ using CSF.Screenplay; +#if SPECFLOW using TechTalk.SpecFlow.Plugins; +#else +using Reqnroll.Plugins; +#endif [assembly: RuntimePlugin(typeof(ScreenplayPlugin))] diff --git a/CSF.Screenplay.Reqnroll/README.md b/CSF.Screenplay.Reqnroll/README.md new file mode 100644 index 00000000..5f9c46eb --- /dev/null +++ b/CSF.Screenplay.Reqnroll/README.md @@ -0,0 +1,19 @@ +# CSF.Screenplay.Reqnroll + +**CSF.Screenplay.Reqnroll** is a [Test Framework Integration] which allows Screenplay-based logic/syntax to be used with tests written for [Reqnroll], which is the maintained fork of an older project named SpecFlow. + +Learn more at the [Getting Started with Reqnroll documentation page]. + +[Test Framework Integration]: https://csf-dev.github.io/CSF.Screenplay/glossary/Integration.html +[Reqnroll]: https://reqnroll.net/ +[Getting Started with Reqnroll documentation page]: https://csf-dev.github.io/CSF.Screenplay/docs/gettingStarted/reqnroll/index.html + +## Source code note + +The source code for this library/project is shared with [CSF.Screenplay.SpecFlow]. +The SpecFlow project has no distinct source code of its own, instead linking to this project's sources. + +The compiler symbol `SPECFLOW` is used to identify the small number of places where code differences are required to cater for SpecFlow. +These are limited to minor differences in naming (primarily .NET namespaces). + +[CSF.Screenplay.SpecFlow]: https://www.nuget.org/packages/CSF.Screenplay.SpecFlow diff --git a/CSF.Screenplay.SpecFlow/ScenarioAndFeatureContextKey.cs b/CSF.Screenplay.Reqnroll/ScenarioAndFeatureContextKey.cs similarity index 77% rename from CSF.Screenplay.SpecFlow/ScenarioAndFeatureContextKey.cs rename to CSF.Screenplay.Reqnroll/ScenarioAndFeatureContextKey.cs index 0f580858..5266343c 100644 --- a/CSF.Screenplay.SpecFlow/ScenarioAndFeatureContextKey.cs +++ b/CSF.Screenplay.Reqnroll/ScenarioAndFeatureContextKey.cs @@ -1,26 +1,30 @@ using System; +#if SPECFLOW using TechTalk.SpecFlow; +#else +using Reqnroll; +#endif namespace CSF.Screenplay { /// /// Simple key class for a combination of Scenario & Feature contexts. /// - internal sealed class ScenarioAndFeatureContextKey : IEquatable + internal sealed class ScenarioAndFeatureInfoKey : IEquatable { readonly int cachedHashCode; /// Gets the scenario - public ScenarioContext Scenario { get; } + public ScenarioInfo Scenario { get; } /// Gets the feature - public FeatureContext Feature { get; } + public FeatureInfo Feature { get; } /// - public override bool Equals(object obj) => Equals(obj as ScenarioAndFeatureContextKey); + public override bool Equals(object obj) => Equals(obj as ScenarioAndFeatureInfoKey); /// - public bool Equals(ScenarioAndFeatureContextKey other) + public bool Equals(ScenarioAndFeatureInfoKey other) { if(ReferenceEquals(this, other)) return true; if(ReferenceEquals(null, other)) return false; @@ -32,12 +36,12 @@ public bool Equals(ScenarioAndFeatureContextKey other) public override int GetHashCode() => cachedHashCode; /// - /// Initializes a new instance of . + /// Initializes a new instance of . /// /// The scenario /// The feature /// If either parameter is . - public ScenarioAndFeatureContextKey(ScenarioContext scenario, FeatureContext feature) + public ScenarioAndFeatureInfoKey(ScenarioInfo scenario, FeatureInfo feature) { if(scenario is null) throw new ArgumentNullException(nameof(scenario)); if(feature is null) throw new ArgumentNullException(nameof(feature)); diff --git a/CSF.Screenplay.SpecFlow/ScreenplayBinding.cs b/CSF.Screenplay.Reqnroll/ScreenplayBinding.cs similarity index 95% rename from CSF.Screenplay.SpecFlow/ScreenplayBinding.cs rename to CSF.Screenplay.Reqnroll/ScreenplayBinding.cs index 77caaa50..583a9e54 100644 --- a/CSF.Screenplay.SpecFlow/ScreenplayBinding.cs +++ b/CSF.Screenplay.Reqnroll/ScreenplayBinding.cs @@ -1,11 +1,15 @@ using System; using Microsoft.Extensions.DependencyInjection; +#if SPECFLOW using TechTalk.SpecFlow; +#else +using Reqnroll; +#endif namespace CSF.Screenplay { /// - /// SpecFlow binding which uses hooks to coordinate the relevant & event invokers. + /// Reqnroll binding which uses hooks to coordinate the relevant & event invokers. /// [Binding] public class ScreenplayBinding diff --git a/CSF.Screenplay.SpecFlow/ScreenplayPlugin.cs b/CSF.Screenplay.Reqnroll/ScreenplayPlugin.cs similarity index 66% rename from CSF.Screenplay.SpecFlow/ScreenplayPlugin.cs rename to CSF.Screenplay.Reqnroll/ScreenplayPlugin.cs index 99b22958..13b5c0d6 100644 --- a/CSF.Screenplay.SpecFlow/ScreenplayPlugin.cs +++ b/CSF.Screenplay.Reqnroll/ScreenplayPlugin.cs @@ -1,25 +1,32 @@ using System; using System.Collections.Concurrent; using System.Reflection; -using BoDi; using CSF.Screenplay.Actors; using CSF.Screenplay.Performances; +#if SPECFLOW +using BoDi; using TechTalk.SpecFlow; using TechTalk.SpecFlow.Plugins; using TechTalk.SpecFlow.UnitTestProvider; +#else +using Reqnroll.BoDi; +using Reqnroll; +using Reqnroll.Plugins; +using Reqnroll.UnitTestProvider; +#endif namespace CSF.Screenplay { /// - /// The Screenplay plugin for SpecFlow. + /// The Screenplay plugin for Reqnroll. /// /// /// - /// This plugin class is the for SpecFlow. - /// Crucially it adds the Screenplay architecture to the SpecFlow architecture. + /// This plugin class is the for Reqnroll. + /// Crucially it adds the Screenplay architecture to the Reqnroll architecture. /// /// - /// Becuase this plugin leverages the SpecFlow/BoDi IObjectContainer, it is likely incompatible with other plugins + /// Becuase this plugin leverages the Reqnroll/BoDi IObjectContainer, it is likely incompatible with other plugins /// which integrate with third party Dependency Injection libraries. /// /// @@ -31,16 +38,16 @@ namespace CSF.Screenplay /// /// If you wish to further customise the dependency injection, such as adding injectable services for /// or implementations of , add them to the relevant DI container. - /// When using SpecFlow's default BoDi container this is described in the following article - /// . + /// When using Reqnroll's default BoDi container this is described in the following article + /// . /// If using a third-party DI container then you should use that container's appropriate mechanism of adding services. /// /// public class ScreenplayPlugin : IRuntimePlugin { readonly object syncRoot = new object(); - readonly ConcurrentDictionary featureContextIds = new ConcurrentDictionary(); - readonly ConcurrentDictionary scenarioContextIds = new ConcurrentDictionary(); + readonly ConcurrentDictionary featureContextIds = new ConcurrentDictionary(); + readonly ConcurrentDictionary scenarioContextIds = new ConcurrentDictionary(); bool initialised; @@ -49,8 +56,9 @@ public class ScreenplayPlugin : IRuntimePlugin /// /// /// - /// This is required because the bindings for beginning/ending the Screenplay in must be static: - /// . + /// This is required because the bindings for beginning/ending the Screenplay in must be static. + /// Those bindings use the [BeforeTestRun] and [AfterTestRun] hooks, which must be static, as documented here: + /// /// /// static public Screenplay Screenplay { get; private set; } @@ -67,7 +75,13 @@ public void Initialize(RuntimePluginEvents runtimePluginEvents, static void OnConfigurationDefaults(object sender, ConfigurationDefaultsEventArgs e) { - e.SpecFlowConfiguration.AdditionalStepAssemblies.Add(Assembly.GetExecutingAssembly().FullName); + var config = +#if SPECFLOW + e.SpecFlowConfiguration; +#else + e.ReqnrollConfiguration; +#endif + config.AdditionalStepAssemblies.Add(Assembly.GetExecutingAssembly().FullName); } /// @@ -75,9 +89,10 @@ static void OnConfigurationDefaults(object sender, ConfigurationDefaultsEventArg /// /// /// - /// It is a known/documented issue that this event may be triggered more than once in a single run of SpecFlow: - /// , and by more than one thread. - /// Thus, to prevent double-initialisation, this method occurs in a thread-safe manner which ensures that even if it + /// It was a known/documented issue that this event may be triggered more than once in a single run of the old + /// SpecFlow, and by more than one thread. Unfortunately I don't know if that's still applicable in Reqnroll. + /// I've opened this discussion to see if I can find out: . + /// To prevent double-initialisation, this method occurs in a thread-safe manner which ensures that even if it /// is executed more than once, there is no adverse consequence. /// /// @@ -115,24 +130,24 @@ void OnCustomizeScenarioDependencies(object sender, CustomizeScenarioDependencie IdentifierAndName GetFeatureIdAndName(IObjectContainer container) { - var featureContext = container.Resolve(); - return new IdentifierAndName(GetFeatureId(featureContext).ToString(), - featureContext.FeatureInfo.Title, + var featureInfo = container.Resolve(); + return new IdentifierAndName(GetFeatureId(featureInfo).ToString(), + featureInfo.Title, true); } - Guid GetFeatureId(FeatureContext featureContext) => featureContextIds.GetOrAdd(featureContext, _ => Guid.NewGuid()); + Guid GetFeatureId(FeatureInfo featureContext) => featureContextIds.GetOrAdd(featureContext, _ => Guid.NewGuid()); IdentifierAndName GetScenarioIdAndName(IObjectContainer container) { - var featureContext = container.Resolve(); - var scenarioContext = container.Resolve(); - return new IdentifierAndName(GetScenarioId(featureContext, scenarioContext).ToString(), - scenarioContext.ScenarioInfo.Title, + var featureInfo = container.Resolve(); + var scenarioInfo = container.Resolve(); + return new IdentifierAndName(GetScenarioId(featureInfo, scenarioInfo).ToString(), + scenarioInfo.Title, true); } - Guid GetScenarioId(FeatureContext featureContext, ScenarioContext scenarioContext) - => scenarioContextIds.GetOrAdd(new ScenarioAndFeatureContextKey(scenarioContext, featureContext), _ => Guid.NewGuid()); + Guid GetScenarioId(FeatureInfo featureInfo, ScenarioInfo scenarioInfo) + => scenarioContextIds.GetOrAdd(new ScenarioAndFeatureInfoKey(scenarioInfo, featureInfo), _ => Guid.NewGuid()); } } diff --git a/CSF.Screenplay.SpecFlow/ScreenplaySteps.cs b/CSF.Screenplay.Reqnroll/ScreenplaySteps.cs similarity index 80% rename from CSF.Screenplay.SpecFlow/ScreenplaySteps.cs rename to CSF.Screenplay.Reqnroll/ScreenplaySteps.cs index 9a5debec..7faa1a0e 100644 --- a/CSF.Screenplay.SpecFlow/ScreenplaySteps.cs +++ b/CSF.Screenplay.Reqnroll/ScreenplaySteps.cs @@ -1,4 +1,5 @@ -using CSF.Screenplay.Actors; +#if SPECFLOW +using CSF.Screenplay.Actors; using TechTalk.SpecFlow; namespace CSF.Screenplay @@ -8,6 +9,9 @@ namespace CSF.Screenplay /// /// /// + /// Note that this class does not exist in the Reqnroll Test Framework Integration. It is irrelevant there. + /// + /// /// In SpecFlow 3.x, the Steps class has three methods named Given, When & Then which can cause /// a naming conflict with the same-named methods of . If you using the performance starter in /// the recommended way, then the methods of these two types can become ambiguous and force the developer to write additional @@ -18,13 +22,10 @@ namespace CSF.Screenplay /// but in a manner which will not cause a name-resolution conflict. /// /// - /// Note that in SpecFlow 4.x and up, this problem is irrelevant; there is no gain in using this subclass over the using static technique. - /// As noted here - /// and here the Give, When & Then methods upon the - /// SpecFlow Steps class were removed in v4.x. This means that the naming conflict won't be present and that there is no need - /// for your bindings to derive from this class instead of the official Steps class. - /// Indeed, Screenplay could be described as a specific implementation of the 'driver pattern', which is noted in the linked Github issue - /// as a best-practice alternative to calling-steps-from-steps. + /// Note that in SpecFlow 4.x and up, and Reqnroll, this problem is irrelevant; there is no gain in using this subclass over the using static technique. + /// The Given, When & Then methods upon the SpecFlow Steps class were removed in v4.x, and never existed in any version of Reqnroll. + /// This means that the naming conflict won't be present and that there is no need for your bindings to derive from this class instead + /// of the official Steps class. /// /// public abstract class ScreenplaySteps : Steps @@ -71,3 +72,4 @@ public abstract class ScreenplaySteps : Steps public static ICanPerformThen Then(Actor actor) => PerformanceStarter.Then(actor); } } +#endif diff --git a/CSF.Screenplay.SpecFlow/ServiceCollectionAdapter.cs b/CSF.Screenplay.Reqnroll/ServiceCollectionAdapter.cs similarity index 98% rename from CSF.Screenplay.SpecFlow/ServiceCollectionAdapter.cs rename to CSF.Screenplay.Reqnroll/ServiceCollectionAdapter.cs index 6f1896ee..11b40faf 100644 --- a/CSF.Screenplay.SpecFlow/ServiceCollectionAdapter.cs +++ b/CSF.Screenplay.Reqnroll/ServiceCollectionAdapter.cs @@ -2,13 +2,17 @@ using System.Collections; using System.Collections.Generic; using System.Reflection; +#if SPECFLOW using BoDi; +#else +using Reqnroll.BoDi; +#endif using Microsoft.Extensions.DependencyInjection; namespace CSF.Screenplay { /// - /// Adapter class which allows a SpecFlow/BoDi IObjectContainer to be used as an . + /// Adapter class which allows a Reqnroll/BoDi IObjectContainer to be used as an . /// /// /// @@ -64,7 +68,7 @@ public ServiceDescriptor this[int index] /// is then the service descriptor will not be added and will be silently ignored. /// /// - /// In reality, the Specflow BoDi object container only really supports singleton services at this level. So, any services added as + /// In reality, the Reqnroll BoDi object container only really supports singleton services at this level. So, any services added as /// will actually become singletons here. Whilst the BoDi container does support scoped services, they /// must be added directly to the scope instance and cannot be added in advance. /// diff --git a/CSF.Screenplay.SpecFlow/ServiceProviderAdapter.cs b/CSF.Screenplay.Reqnroll/ServiceProviderAdapter.cs similarity index 88% rename from CSF.Screenplay.SpecFlow/ServiceProviderAdapter.cs rename to CSF.Screenplay.Reqnroll/ServiceProviderAdapter.cs index 9c45cc11..4684d5c1 100644 --- a/CSF.Screenplay.SpecFlow/ServiceProviderAdapter.cs +++ b/CSF.Screenplay.Reqnroll/ServiceProviderAdapter.cs @@ -1,10 +1,15 @@ using System; +#if SPECFLOW using BoDi; +#else +using Reqnroll.BoDi; +#endif + namespace CSF.Screenplay { /// - /// Adapter class which allows a SpecFlow/BoDi IObjectContainer to be used as an . + /// Adapter class which allows a Reqnroll/BoDi IObjectContainer to be used as an . /// public class ServiceProviderAdapter : IServiceProvider { diff --git a/CSF.Screenplay.SpecFlow/CSF.Screenplay.SpecFlow.csproj b/CSF.Screenplay.SpecFlow/CSF.Screenplay.SpecFlow.csproj index 056e69f3..fc6dce5d 100644 --- a/CSF.Screenplay.SpecFlow/CSF.Screenplay.SpecFlow.csproj +++ b/CSF.Screenplay.SpecFlow/CSF.Screenplay.SpecFlow.csproj @@ -11,6 +11,7 @@ false CSF.Screenplay.SpecFlowPlugin A Test Framework Integration for CSF.Screenplay for the legacy SpecFlow 3.4.3+ (recommendation: Switch to Reqnroll) + $(DefineConstants);SPECFLOW @@ -22,4 +23,15 @@ + + + + + diff --git a/CSF.Screenplay.SpecFlow/README.md b/CSF.Screenplay.SpecFlow/README.md index 1ba1d360..0c54b887 100644 --- a/CSF.Screenplay.SpecFlow/README.md +++ b/CSF.Screenplay.SpecFlow/README.md @@ -1,11 +1,26 @@ # CSF.Screenplay.SpecFlow -**CSF.Screenplay.SpecFlow** is a [Test Framework Integration] which allows Screenplay-based logic/syntax to be used with tests written in the [legacy SpecFlow, *which is now retired*]. +**CSF.Screenplay.SpecFlow** is a [Test Framework Integration] which allows Screenplay-based logic/syntax to be used with tests written for the [legacy SpecFlow, *which is now retired*]. Learn more at the [Getting Started with Reqnroll documentation page]. [Reqnroll] is the maintained fork of SpecFlow, supported by its original authors. +At present, documentation for Reqnroll is equally applicable to SpecFlow. +Nevertheless, it is recommended to migrate SpecFlow projects to Reqnroll ASAP. +It is not assured that extensibility will remain compatible between the two projects. +Maintaining support for SpecFlow is not a priority for CSF.Screenplay; *support may end as Reqnroll diverges further*. + [Test Framework Integration]: https://csf-dev.github.io/CSF.Screenplay/glossary/Integration.html [legacy SpecFlow, *which is now retired*]: https://reqnroll.net/news/2025/01/specflow-end-of-life-has-been-announced/ [Getting Started with Reqnroll documentation page]: https://csf-dev.github.io/CSF.Screenplay/docs/gettingStarted/reqnroll/index.html [Reqnroll]: https://reqnroll.net/ + +## Source code note + +Browsing the repository/source code for this package/library, you will notice that there is none! +The source code for this Test Framework Integration and [CSF.Screenplay.Reqnroll] are identical, and is maintained in the CSF.Screenplay.Reqnroll project. + +The compiler symbol `SPECFLOW` is used to identify the small number of places where code differences are required to cater for SpecFlow. +These are limited to minor differences in naming (primarily .NET namespaces). + +[CSF.Screenplay.Reqnroll]: https://www.nuget.org/packages/CSF.Screenplay.Reqnroll diff --git a/CSF.Screenplay.sln b/CSF.Screenplay.sln index c74faf75..4f45b46d 100644 --- a/CSF.Screenplay.sln +++ b/CSF.Screenplay.sln @@ -31,6 +31,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CSF.Screenplay.Selenium.Tes EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CSF.Screenplay.Selenium.TestWebapp", "Tests\CSF.Screenplay.Selenium.TestWebapp\CSF.Screenplay.Selenium.TestWebapp.csproj", "{B5C9CDE0-9EAB-4381-8C79-D3F2ED1A09D2}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CSF.Screenplay.Reqnroll", "CSF.Screenplay.Reqnroll\CSF.Screenplay.Reqnroll.csproj", "{0ABCCC06-1BC6-493A-8192-B964DC83D3A2}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CSF.Screenplay.Reqnroll.Tests", "Tests\CSF.Screenplay.Reqnroll.Tests\CSF.Screenplay.Reqnroll.Tests.csproj", "{A3D505D9-FE84-4032-8356-B90A30A8E4C4}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -122,6 +126,18 @@ Global {B5C9CDE0-9EAB-4381-8C79-D3F2ED1A09D2}.Release|Any CPU.Build.0 = Release|Any CPU {B5C9CDE0-9EAB-4381-8C79-D3F2ED1A09D2}.Docs|Any CPU.ActiveCfg = Debug|Any CPU {B5C9CDE0-9EAB-4381-8C79-D3F2ED1A09D2}.Docs|Any CPU.Build.0 = Debug|Any CPU + {0ABCCC06-1BC6-493A-8192-B964DC83D3A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0ABCCC06-1BC6-493A-8192-B964DC83D3A2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0ABCCC06-1BC6-493A-8192-B964DC83D3A2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0ABCCC06-1BC6-493A-8192-B964DC83D3A2}.Release|Any CPU.Build.0 = Release|Any CPU + {0ABCCC06-1BC6-493A-8192-B964DC83D3A2}.Docs|Any CPU.ActiveCfg = Debug|Any CPU + {0ABCCC06-1BC6-493A-8192-B964DC83D3A2}.Docs|Any CPU.Build.0 = Debug|Any CPU + {A3D505D9-FE84-4032-8356-B90A30A8E4C4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A3D505D9-FE84-4032-8356-B90A30A8E4C4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A3D505D9-FE84-4032-8356-B90A30A8E4C4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A3D505D9-FE84-4032-8356-B90A30A8E4C4}.Release|Any CPU.Build.0 = Release|Any CPU + {A3D505D9-FE84-4032-8356-B90A30A8E4C4}.Docs|Any CPU.ActiveCfg = Debug|Any CPU + {A3D505D9-FE84-4032-8356-B90A30A8E4C4}.Docs|Any CPU.Build.0 = Debug|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {7369BCFA-DA98-4C05-9708-7949AF3E485B} = {2C3ABE29-B5F7-404E-B20A-E3679E2ECEC5} @@ -129,5 +145,6 @@ Global {9D416E8D-EB56-4616-890B-71691FC3582E} = {2C3ABE29-B5F7-404E-B20A-E3679E2ECEC5} {AEF2B39D-5129-41DA-AD51-02C8617A344F} = {2C3ABE29-B5F7-404E-B20A-E3679E2ECEC5} {B5C9CDE0-9EAB-4381-8C79-D3F2ED1A09D2} = {2C3ABE29-B5F7-404E-B20A-E3679E2ECEC5} + {A3D505D9-FE84-4032-8356-B90A30A8E4C4} = {2C3ABE29-B5F7-404E-B20A-E3679E2ECEC5} EndGlobalSection EndGlobal diff --git a/CSF.Screenplay/ScreenplayOptions.cs b/CSF.Screenplay/ScreenplayOptions.cs index 69c6cfe5..ed22076e 100644 --- a/CSF.Screenplay/ScreenplayOptions.cs +++ b/CSF.Screenplay/ScreenplayOptions.cs @@ -14,7 +14,7 @@ namespace CSF.Screenplay /// /// Developer note: In an ideal world, this type would be registered into DI via the Options pattern: /// . - /// Unfortunately, the BoDi DI container used by SpecFlow (which I wish to support with Screenplay) does not support + /// Unfortunately, the BoDi DI container used by Reqnroll (which I wish to support with Screenplay) does not support /// the appropriate methods/logic to register the neccesary services for Options. /// This is why this object uses a somewhat homebrew version of options, without making use of the official libraries. /// diff --git a/Tests/CSF.Screenplay.Reqnroll.Tests/AddingUp.feature b/Tests/CSF.Screenplay.Reqnroll.Tests/AddingUp.feature new file mode 100644 index 00000000..4401008a --- /dev/null +++ b/Tests/CSF.Screenplay.Reqnroll.Tests/AddingUp.feature @@ -0,0 +1,19 @@ +Feature: Adding up numbers + + As a math idiot, I want to be able to add numbers to a running total, to test the Screenplay/Reqnroll integration + +Scenario: Mathias should be able to add two numbers together and get the correct result + Given Mathias has the number 50 + When he adds 70 + Then he should have the total 120 + +Scenario: Mathias should be able to add three numbers together and get the correct result + Given Mathias has the number 50 + When he adds 70 + And he adds 20 + Then he should have the total 140 + +Scenario: Mathias should be able to add three numbers in a single step + Given Mathias has the number 50 + When he adds 40, 30 and 20 + Then he should have the total 140 \ No newline at end of file diff --git a/Tests/CSF.Screenplay.Reqnroll.Tests/AddingUp/AddNumbers.cs b/Tests/CSF.Screenplay.Reqnroll.Tests/AddingUp/AddNumbers.cs new file mode 100644 index 00000000..dff5e29d --- /dev/null +++ b/Tests/CSF.Screenplay.Reqnroll.Tests/AddingUp/AddNumbers.cs @@ -0,0 +1,15 @@ +namespace CSF.Screenplay.AddingUp; + +public class AddNumbers : ICanReport +{ + int currentNumber; + + public ReportFragment GetReportFragment(Actor actor, IFormatsReportFragment formatter) + => formatter.Format("{Actor} is able to add numbers to a running total", actor); + + public void Add(int number) => currentNumber += number; + + public void Set(int number) => currentNumber = number; + + public int Get() => currentNumber; +} \ No newline at end of file diff --git a/Tests/CSF.Screenplay.Reqnroll.Tests/AddingUp/AddTheNumber.cs b/Tests/CSF.Screenplay.Reqnroll.Tests/AddingUp/AddTheNumber.cs new file mode 100644 index 00000000..04fa264c --- /dev/null +++ b/Tests/CSF.Screenplay.Reqnroll.Tests/AddingUp/AddTheNumber.cs @@ -0,0 +1,14 @@ +namespace CSF.Screenplay.AddingUp; + +public class AddTheNumber(int number) : IPerformable, ICanReport +{ + public ReportFragment GetReportFragment(Actor actor, IFormatsReportFragment formatter) + => formatter.Format("{Actor} adds {Aumber} to the running total", actor, number); + + public ValueTask PerformAsAsync(ICanPerform actor, CancellationToken cancellationToken = default) + { + var ability = actor.GetAbility(); + ability.Add(number); + return default; + } +} diff --git a/Tests/CSF.Screenplay.Reqnroll.Tests/AddingUp/AddThreeNumbers.cs b/Tests/CSF.Screenplay.Reqnroll.Tests/AddingUp/AddThreeNumbers.cs new file mode 100644 index 00000000..b3cca04a --- /dev/null +++ b/Tests/CSF.Screenplay.Reqnroll.Tests/AddingUp/AddThreeNumbers.cs @@ -0,0 +1,17 @@ +using static CSF.Screenplay.AddingUp.AddingUpBuilder; + +namespace CSF.Screenplay.AddingUp +{ + public class AddThreeNumbers(int number1, int number2, int number3) : IPerformable, ICanReport + { + public ReportFragment GetReportFragment(Actor actor, IFormatsReportFragment formatter) + => formatter.Format("{Actor} adds three numbers to the running total: {One}, {Two} & {Three}", actor, number1, number2, number3); + + public async ValueTask PerformAsAsync(ICanPerform actor, CancellationToken cancellationToken = default) + { + await actor.PerformAsync(Add(number1), cancellationToken); + await actor.PerformAsync(Add(number2), cancellationToken); + await actor.PerformAsync(Add(number3), cancellationToken); + } + } +} \ No newline at end of file diff --git a/Tests/CSF.Screenplay.Reqnroll.Tests/AddingUp/AddingUpBuilder.cs b/Tests/CSF.Screenplay.Reqnroll.Tests/AddingUp/AddingUpBuilder.cs new file mode 100644 index 00000000..e0c9de5c --- /dev/null +++ b/Tests/CSF.Screenplay.Reqnroll.Tests/AddingUp/AddingUpBuilder.cs @@ -0,0 +1,9 @@ +namespace CSF.Screenplay.AddingUp; + +public static class AddingUpBuilder +{ + public static IPerformable Add(int number) => new AddTheNumber(number); + public static IPerformable AddThreeNumbers(int number1, int number2, int number3) => new AddThreeNumbers(number1, number2, number3); + public static IPerformable SetTheTotalTo(int number) => new SetTheNumber(number); + public static IPerformableWithResult GetTheTotal() => new GetTheNumber(); +} diff --git a/Tests/CSF.Screenplay.Reqnroll.Tests/AddingUp/GetTheNumber.cs b/Tests/CSF.Screenplay.Reqnroll.Tests/AddingUp/GetTheNumber.cs new file mode 100644 index 00000000..56d5806c --- /dev/null +++ b/Tests/CSF.Screenplay.Reqnroll.Tests/AddingUp/GetTheNumber.cs @@ -0,0 +1,13 @@ +namespace CSF.Screenplay.AddingUp; + +public class GetTheNumber() : IPerformableWithResult, ICanReport +{ + public ReportFragment GetReportFragment(Actor actor, IFormatsReportFragment formatter) + => formatter.Format("{Actor} gets the running total", actor); + + ValueTask IPerformableWithResult.PerformAsAsync(ICanPerform actor, CancellationToken cancellationToken) + { + var ability = actor.GetAbility(); + return ValueTask.FromResult(ability.Get()); + } +} \ No newline at end of file diff --git a/Tests/CSF.Screenplay.Reqnroll.Tests/AddingUp/SetTheNumber.cs b/Tests/CSF.Screenplay.Reqnroll.Tests/AddingUp/SetTheNumber.cs new file mode 100644 index 00000000..6d292e6d --- /dev/null +++ b/Tests/CSF.Screenplay.Reqnroll.Tests/AddingUp/SetTheNumber.cs @@ -0,0 +1,14 @@ +namespace CSF.Screenplay.AddingUp; + +public class SetTheNumber(int number) : IPerformable, ICanReport +{ + public ReportFragment GetReportFragment(Actor actor, IFormatsReportFragment formatter) + => formatter.Format("{Actor} sets the running total to {Number}", actor, number); + + public ValueTask PerformAsAsync(ICanPerform actor, CancellationToken cancellationToken = default) + { + var ability = actor.GetAbility(); + ability.Set(number); + return default; + } +} diff --git a/Tests/CSF.Screenplay.Reqnroll.Tests/CSF.Screenplay.Reqnroll.Tests.csproj b/Tests/CSF.Screenplay.Reqnroll.Tests/CSF.Screenplay.Reqnroll.Tests.csproj new file mode 100644 index 00000000..007fedb8 --- /dev/null +++ b/Tests/CSF.Screenplay.Reqnroll.Tests/CSF.Screenplay.Reqnroll.Tests.csproj @@ -0,0 +1,28 @@ + + + + net8.0 + enable + false + true + CSF.Screenplay + + + + + + + + + + + + + + + + + + + + diff --git a/Tests/CSF.Screenplay.Reqnroll.Tests/GlobalUsings.cs b/Tests/CSF.Screenplay.Reqnroll.Tests/GlobalUsings.cs new file mode 100644 index 00000000..e0a091ee --- /dev/null +++ b/Tests/CSF.Screenplay.Reqnroll.Tests/GlobalUsings.cs @@ -0,0 +1,4 @@ +global using NUnit.Framework; +global using System.Threading; +global using System.Threading.Tasks; +global using Reqnroll; diff --git a/Tests/CSF.Screenplay.Reqnroll.Tests/Mathias.cs b/Tests/CSF.Screenplay.Reqnroll.Tests/Mathias.cs new file mode 100644 index 00000000..0f400139 --- /dev/null +++ b/Tests/CSF.Screenplay.Reqnroll.Tests/Mathias.cs @@ -0,0 +1,16 @@ +using System; +using CSF.Screenplay.AddingUp; + +namespace CSF.Screenplay; + +public class Mathias : IPersona +{ + public string Name => "Mathias"; + + public Actor GetActor(Guid performanceIdentity) + { + var mathias = new Actor(Name, performanceIdentity); + mathias.IsAbleTo(); + return mathias; + } +} \ No newline at end of file diff --git a/Tests/CSF.Screenplay.Reqnroll.Tests/ServiceCollectionAdapterTests.cs b/Tests/CSF.Screenplay.Reqnroll.Tests/ServiceCollectionAdapterTests.cs new file mode 100644 index 00000000..a28e5202 --- /dev/null +++ b/Tests/CSF.Screenplay.Reqnroll.Tests/ServiceCollectionAdapterTests.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections; +using Reqnroll.BoDi; +using Microsoft.Extensions.DependencyInjection; + +namespace CSF.Screenplay; + +[TestFixture,Parallelizable] +public class ServiceCollectionAdapterTests +{ + [Test] + public void UnsupportedFunctionalityShouldThrowNotSupportedException() + { + var container = new ObjectContainer(); + var sut = new ServiceCollectionAdapter(container); + + Assert.Multiple(() => + { + Assert.That(() => sut[0], Throws.InstanceOf(), "Indexer get"); + Assert.That(() => sut[0] = null, Throws.InstanceOf(), "Indexer set"); + Assert.That(() => sut.Clear(), Throws.InstanceOf(), nameof(IServiceCollection.Clear)); + Assert.That(() => sut.Contains(null), Throws.InstanceOf(), nameof(IServiceCollection.Contains)); + Assert.That(() => sut.CopyTo(null, default), Throws.InstanceOf(), nameof(IServiceCollection.CopyTo)); + Assert.That(() => sut.GetEnumerator(), Throws.InstanceOf(), nameof(IServiceCollection.GetEnumerator)); + Assert.That(() => ((IEnumerable) sut).GetEnumerator(), Throws.InstanceOf(), nameof(IServiceCollection.GetEnumerator) + ": " + nameof(IEnumerable)); + Assert.That(() => sut.IndexOf(default), Throws.InstanceOf(), nameof(IServiceCollection.IndexOf)); + Assert.That(() => sut.Insert(default, null), Throws.InstanceOf(), nameof(IServiceCollection.Insert)); + Assert.That(() => sut.Remove(null), Throws.InstanceOf(), nameof(IServiceCollection.Remove)); + Assert.That(() => sut.RemoveAt(default), Throws.InstanceOf(), nameof(IServiceCollection.RemoveAt)); + }); + } + + [Test] + public void AddTypeShouldAddToObjectContainer() + { + var container = new ObjectContainer(); + var sut = new ServiceCollectionAdapter(container); + + sut.AddSingleton(); + Assert.That(container.Resolve, Is.InstanceOf()); + } + + [Test] + public void AddInstanceShouldAddToObjectContainer() + { + var container = new ObjectContainer(); + var sut = new ServiceCollectionAdapter(container); + + var instance = new SampleType(); + sut.AddSingleton(instance); + Assert.That(container.Resolve(), Is.SameAs(instance)); + } + + [Test] + public void AddFactoryShouldAddToObjectContainer() + { + var container = new ObjectContainer(); + var sut = new ServiceCollectionAdapter(container); + + var instance = new SampleType(); + sut.AddSingleton(_ => instance); + Assert.That(container.Resolve(), Is.SameAs(instance)); + } + + interface ISampleInterface {} + + class SampleType : ISampleInterface {} +} \ No newline at end of file diff --git a/Tests/CSF.Screenplay.Reqnroll.Tests/StepDefinitions/AddingUpSteps.cs b/Tests/CSF.Screenplay.Reqnroll.Tests/StepDefinitions/AddingUpSteps.cs new file mode 100644 index 00000000..d44389cb --- /dev/null +++ b/Tests/CSF.Screenplay.Reqnroll.Tests/StepDefinitions/AddingUpSteps.cs @@ -0,0 +1,39 @@ +using static CSF.Screenplay.PerformanceStarter; +using static CSF.Screenplay.AddingUp.AddingUpBuilder; + +namespace CSF.Screenplay.StepDefinitions +{ + [Binding] + public class StepDefinitions(IStage stage) + { + [Given(@"Mathias has the number (\d+)")] + public async Task GivenMathiashasthenumber(int number) + { + var mathias = stage.Spotlight(); + await Given(mathias).WasAbleTo(SetTheTotalTo(number)); + } + + [When(@"(?:he|she|they) adds? (\d+)")] + public async Task Whenheadds(int number) + { + var actor = stage.GetSpotlitActor(); + await When(actor).AttemptsTo(Add(number)); + } + + [When(@"(?:he|she|they) adds? (\d+), (\d+) and (\d+)")] + public async Task Whenheaddsthreenumbers(int number1, int number2, int number3) + { + var actor = stage.GetSpotlitActor(); + await When(actor).AttemptsTo(AddThreeNumbers(number1, number2, number3)); + } + + [Then(@"(?:he|she|they) should have the total (\d+)")] + public async Task Thenheshouldhavethetotal(int number) + { + var actor = stage.GetSpotlitActor(); + var total = await Then(actor).Should(GetTheTotal()); + + Assert.That(total, Is.EqualTo(number)); + } + } +} \ No newline at end of file