Skip to content

fix: throw ArgumentException when expected is empty in IsOneOf (2)#671

Merged
vbreuss merged 3 commits intomainfrom
topic/refactor-isoneof
Jun 30, 2025
Merged

fix: throw ArgumentException when expected is empty in IsOneOf (2)#671
vbreuss merged 3 commits intomainfrom
topic/refactor-isoneof

Conversation

@vbreuss
Copy link
Copy Markdown
Member

@vbreuss vbreuss commented Jun 30, 2025

This PR introduces consistent argument validation for empty expected collections in various IsOneOf and IsNotOneOf constraints, adds corresponding tests, and addresses Sonar issues introduced in #669 by centralizing the exception throw.

  • Added tests that verify ArgumentException is thrown when the expected collection is empty for both IsOneOf and IsNotOneOf across strings, objects, enums, etc.
  • Refactored IsOneOf implementations to manually iterate, detect empty inputs, and use a new ThrowHelper.EmptyCollection() helper.
  • Removed LINQ usage in these methods and cleaned up trailing commas in collection-expression literals.

vbreuss added 2 commits June 30, 2025 07:48
Also throw the `ArgumentException` for empty expected values for enum, object and string and fix the sonar issues introduced in #669.
@vbreuss vbreuss self-assigned this Jun 30, 2025
Copilot AI review requested due to automatic review settings June 30, 2025 06:02
@vbreuss vbreuss added bug Something isn't working refactor A change or improvement without functional impact labels Jun 30, 2025
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR introduces consistent argument validation for empty expected collections in various IsOneOf and IsNotOneOf constraints, adds corresponding tests, and addresses Sonar issues by centralizing the exception throw.

  • Added tests that verify ArgumentException is thrown when the expected collection is empty for both IsOneOf and IsNotOneOf across strings, objects, enums, etc.
  • Refactored IsOneOf implementations to manually iterate, detect empty inputs, and use a new ThrowHelper.EmptyCollection() helper.
  • Removed LINQ usage in these methods and cleaned up trailing commas in collection-expression literals.

Reviewed Changes

Copilot reviewed 26 out of 26 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
Tests/aweXpect.Tests/Strings/ThatString.IsOneOf.Tests.cs Added tests for empty and nullable-empty expected.
Tests/aweXpect.Tests/Strings/ThatString.IsNotOneOf.Tests.cs Added tests for empty and nullable-empty unexpected.
Tests/aweXpect.Tests/Objects/ThatObject.IsOneOf.Tests.cs Added tests for empty and nullable-empty expected.
Tests/aweXpect.Tests/Objects/ThatObject.IsNotOneOf.Tests.cs Added tests for empty and nullable-empty unexpected.
Tests/aweXpect.Tests/Enums/ThatEnum.Nullable.IsOneOf.Tests.cs Added empty-expected tests for nullable enums.
Tests/aweXpect.Tests/Enums/ThatEnum.Nullable.IsNotOneOf.Tests.cs Added empty-expected tests for nullable enums.
Tests/aweXpect.Tests/Enums/ThatEnum.IsOneOf.Tests.cs Added empty-expected tests for enums.
Tests/aweXpect.Tests/Enums/ThatEnum.IsNotOneOf.Tests.cs Added empty-expected tests for enums.
Source/aweXpect/That/TimeSpans/ThatTimeSpan.IsOneOf.cs Replaced inline throw with ThrowHelper.EmptyCollection().
Source/aweXpect/That/TimeSpans/ThatNullableTimeSpan.IsOneOf.cs Replaced inline throw with ThrowHelper.EmptyCollection().
Source/aweXpect/That/TimeOnlys/ThatTimeOnly.IsOneOf.cs Replaced inline throw with ThrowHelper.EmptyCollection().
Source/aweXpect/That/TimeOnlys/ThatNullableTimeOnly.IsOneOf.cs Replaced inline throw with ThrowHelper.EmptyCollection().
Source/aweXpect/That/Strings/ThatString.IsOneOf.cs Removed LINQ, added manual loop and empty-check throw.
Source/aweXpect/That/Objects/ThatObject.IsOneOf.cs Removed LINQ, added manual loop and empty-check throw.
Source/aweXpect/That/Numbers/ThatNumber.IsOneOf.cs Removed LINQ, added manual loop and empty-check throw.
Source/aweXpect/That/Enums/ThatNullableEnum.IsOneOf.cs Removed LINQ, added manual loop and empty-check throw.
Source/aweXpect/That/Enums/ThatEnum.IsOneOf.cs Removed LINQ, added manual loop and empty-check throw.
Source/aweXpect/That/DateTimes/ThatNullableDateTime.IsOneOf.cs Replaced inline throw with ThrowHelper.EmptyCollection().
Source/aweXpect/That/DateTimes/ThatDateTime.IsOneOf.cs Replaced inline throw with ThrowHelper.EmptyCollection().
Source/aweXpect/That/DateTimeOffsets/ThatNullableDateTimeOffset.IsOneOf.cs Replaced inline throw with ThrowHelper.EmptyCollection().
Source/aweXpect/That/DateTimeOffsets/ThatDateTimeOffset.IsOneOf.cs Replaced inline throw with ThrowHelper.EmptyCollection().
Source/aweXpect/That/DateOnlys/ThatNullableDateOnly.IsOneOf.cs Replaced inline throw with ThrowHelper.EmptyCollection().
Source/aweXpect/That/DateOnlys/ThatDateOnly.IsOneOf.cs Replaced inline throw with ThrowHelper.EmptyCollection().
Source/aweXpect/That/Chars/ThatNullableChar.IsOneOf.cs Replaced inline throw with ThrowHelper.EmptyCollection().
Source/aweXpect/That/Chars/ThatChar.IsOneOf.cs Replaced inline throw with ThrowHelper.EmptyCollection().
Source/aweXpect/Helpers/ThrowHelper.cs Introduced helper for consistent empty-collection exception.
Comments suppressed due to low confidence (1)

Source/aweXpect/That/Numbers/ThatNumber.IsOneOf.cs:219

  • Iterating over expected as non-nullable TNumber can throw when expected contains null; revert to TNumber? in the iteration variable to match the enumerable's nullability.
			foreach (TNumber value in expected)

Comment thread Source/aweXpect/That/Strings/ThatString.IsOneOf.cs
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 30, 2025

Test Results

    14 files   - 24      14 suites   - 24   2m 33s ⏱️ ±0s
13 825 tests  -  6  13 823 ✅  -  6  2 💤 ±0  0 ❌ ±0 
38 376 runs   - 24  38 374 ✅  - 24  2 💤 ±0  0 ❌ ±0 

Results for commit 65e3eea. ± Comparison against base commit 07da697.

This pull request removes 1446 and adds 1440 tests. Note that renamed tests count towards both.
aweXpect.Core.Tests.Core.Exceptions.FailExceptionTests ‑ Message_ShouldBeSet(message: "message61bc53d1-2b94-4403-88f3-a1b9d1444868")
aweXpect.Core.Tests.Core.Exceptions.FailExceptionTests ‑ Message_ShouldBeSet(message: "messaged18973a3-6778-4fd7-8a25-667bae6a13de")
aweXpect.Core.Tests.Core.Exceptions.FailExceptionTests ‑ Message_ShouldBeSet(message: "messagef9f6f5b7-2087-4b19-a9a2-4b4758d54aff")
aweXpect.Core.Tests.Core.Exceptions.SkipExceptionTests ‑ Message_ShouldBeSet(message: "message604f646e-92a7-43c3-9ab6-27e4a32ec093")
aweXpect.Core.Tests.Core.Exceptions.SkipExceptionTests ‑ Message_ShouldBeSet(message: "messagebb5e0ad3-5c74-47ae-ad52-4e076f937f68")
aweXpect.Core.Tests.Core.Exceptions.SkipExceptionTests ‑ Message_ShouldBeSet(message: "messagef6b8ae99-53ee-4331-83b1-20926994e91f")
aweXpect.Core.Tests.Equivalency.EquivalencyOptionsExtensionsTests ‑ Generic_For_IgnoringMember_ShouldSetOptionForType(memberToIgnore: "memberToIgnore3386c480-7017-47bd-a3f4-2974b262c377")
aweXpect.Core.Tests.Equivalency.EquivalencyOptionsExtensionsTests ‑ Generic_For_IgnoringMember_ShouldSetOptionForType(memberToIgnore: "memberToIgnore9b1b0e18-45d9-419f-963d-08f25ac9bb97")
aweXpect.Core.Tests.Equivalency.EquivalencyOptionsExtensionsTests ‑ Generic_For_IgnoringMember_ShouldSetOptionForType(memberToIgnore: "memberToIgnoref2bb99a3-0774-4e2e-a6f6-402eecc7ecee")
aweXpect.Core.Tests.FailTests ‑ Test_ShouldThrowException(reason: "reason21f2321d-2c3a-4700-a448-2b7f820c3914")
…
aweXpect.Core.Tests.Core.Exceptions.FailExceptionTests ‑ Message_ShouldBeSet(message: "message02bfb3c6-1a10-42ce-84bb-6ba88ece4486")
aweXpect.Core.Tests.Core.Exceptions.FailExceptionTests ‑ Message_ShouldBeSet(message: "message24312185-3a86-41e1-bf8b-60f03f4b0bb0")
aweXpect.Core.Tests.Core.Exceptions.FailExceptionTests ‑ Message_ShouldBeSet(message: "message9c15afd3-ac20-4b11-9fd8-7e0a74dd6b6d")
aweXpect.Core.Tests.Core.Exceptions.SkipExceptionTests ‑ Message_ShouldBeSet(message: "message0ea322bb-458f-4cda-bbaa-9a1230554a7e")
aweXpect.Core.Tests.Core.Exceptions.SkipExceptionTests ‑ Message_ShouldBeSet(message: "messaged5ac835f-e96f-4141-b4ae-3cf2e91cee49")
aweXpect.Core.Tests.Core.Exceptions.SkipExceptionTests ‑ Message_ShouldBeSet(message: "messageddc61630-568b-477a-9099-a67a6d30c68e")
aweXpect.Core.Tests.Equivalency.EquivalencyOptionsExtensionsTests ‑ Generic_For_IgnoringMember_ShouldSetOptionForType(memberToIgnore: "memberToIgnore2d377e6e-ff4d-47df-81b0-3a52ade8bd7f")
aweXpect.Core.Tests.Equivalency.EquivalencyOptionsExtensionsTests ‑ Generic_For_IgnoringMember_ShouldSetOptionForType(memberToIgnore: "memberToIgnore4eea658b-07ee-4714-bd16-439e3cfb5650")
aweXpect.Core.Tests.Equivalency.EquivalencyOptionsExtensionsTests ‑ Generic_For_IgnoringMember_ShouldSetOptionForType(memberToIgnore: "memberToIgnorec6c2ab95-2db9-4218-8db4-282481310825")
aweXpect.Core.Tests.FailTests ‑ Test_ShouldThrowException(reason: "reason4d92db76-fd5f-4e29-9967-d247c5bf144f")
…

♻️ This comment has been updated with latest results.

@vbreuss vbreuss enabled auto-merge (squash) June 30, 2025 06:10
@sonarqubecloud
Copy link
Copy Markdown

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 30, 2025

🚀 Benchmark Results

Details

BenchmarkDotNet v0.14.0, Ubuntu 24.04.2 LTS (Noble Numbat)
AMD EPYC 7763, 1 CPU, 4 logical and 2 physical cores
.NET SDK 8.0.411
[Host] : .NET 8.0.17 (8.0.1725.26602), X64 RyuJIT AVX2

Job=InProcess Toolchain=InProcessEmitToolchain IterationCount=15
LaunchCount=1 WarmupCount=10

Method Mean Error StdDev Gen0 Gen1 Allocated
Bool_aweXpect 184.8 ns 3.13 ns 2.92 ns 0.0281 - 472 B
Bool_FluentAssertions 238.5 ns 3.68 ns 3.44 ns 0.0567 - 952 B
Bool_TUnit 945.3 ns 17.52 ns 16.39 ns 0.1440 - 2416 B
Equivalency_aweXpect 298,396.0 ns 885.90 ns 828.67 ns 16.6016 0.4883 284940 B
Equivalency_FluentAssertions 2,308,022.6 ns 8,826.98 ns 8,256.76 ns 273.4375 46.8750 4584416 B
Equivalency_TUnit 677,673.1 ns 3,263.53 ns 3,052.71 ns 51.7578 2.9297 866777 B
Int_GreaterThan_aweXpect 221.9 ns 2.12 ns 1.88 ns 0.0467 - 784 B
Int_GreaterThan_FluentAssertions 266.9 ns 6.42 ns 6.00 ns 0.0730 - 1224 B
Int_GreaterThan_TUnit 1,226.0 ns 10.36 ns 9.69 ns 0.1774 - 2992 B
ItemsCount_AtLeast_aweXpect 501.3 ns 5.53 ns 5.17 ns 0.0849 - 1432 B
ItemsCount_AtLeast_FluentAssertions 494.8 ns 3.34 ns 3.12 ns 0.1192 - 2008 B
ItemsCount_AtLeast_TUnit 15,279.4 ns 225.23 ns 210.68 ns 1.6327 - 27488 B
String_aweXpect 343.8 ns 3.60 ns 3.36 ns 0.0672 - 1128 B
String_FluentAssertions 482.3 ns 5.94 ns 5.55 ns 0.1287 - 2168 B
String_TUnit 1,306.2 ns 7.95 ns 7.05 ns 0.1850 - 3096 B
StringArray_aweXpect 1,344.3 ns 7.51 ns 6.65 ns 0.1640 - 2744 B
StringArray_FluentAssertions 1,382.5 ns 5.25 ns 4.65 ns 0.2480 - 4152 B
StringArray_TUnit 2,809.0 ns 20.24 ns 18.93 ns 0.2708 - 4576 B
StringArrayInAnyOrder_aweXpect 1,623.1 ns 18.62 ns 16.51 ns 0.1736 - 2920 B
StringArrayInAnyOrder_FluentAssertions 150,971.2 ns 806.48 ns 754.38 ns 3.4180 - 63787 B
StringArrayInAnyOrder_TUnit 4,661.9 ns 24.26 ns 21.51 ns 0.3967 - 6744 B

@github-actions
Copy link
Copy Markdown
Contributor

👽 Mutation Results

Mutation testing badge

aweXpect

Details
File Score Killed Survived Timeout No Coverage Ignored Compile Errors Total Detected Total Undetected Total Mutants
Helpers/ThrowHelper.cs 100.00% 1 0 0 0 0 0 1 0 1
That/Chars/ThatChar.IsOneOf.cs 100.00% 15 0 0 0 6 4 15 0 25
That/Chars/ThatNullableChar.IsOneOf.cs 100.00% 15 0 0 0 6 4 15 0 25
That/DateOnlys/ThatDateOnly.IsOneOf.cs 100.00% 29 0 0 0 6 6 29 0 41
That/DateOnlys/ThatNullableDateOnly.IsOneOf.cs 100.00% 34 0 0 0 8 6 34 0 48
That/DateTimeOffsets/ThatDateTimeOffset.IsOneOf.cs 100.00% 31 0 0 0 6 8 31 0 45
That/DateTimeOffsets/ThatNullableDateTimeOffset.IsOneOf.cs 100.00% 36 0 0 0 8 8 36 0 52
That/DateTimes/ThatDateTime.IsOneOf.cs 100.00% 31 0 0 0 6 8 31 0 45
That/DateTimes/ThatNullableDateTime.IsOneOf.cs 100.00% 36 0 0 0 8 8 36 0 52
That/Enums/ThatEnum.IsOneOf.cs 100.00% 15 0 0 0 6 4 15 0 25
That/Enums/ThatNullableEnum.IsOneOf.cs 100.00% 15 0 0 0 6 4 15 0 25
That/Numbers/ThatNumber.IsOneOf.cs 97.50% 78 0 0 2 24 24 78 2 128
That/Objects/ThatObject.IsOneOf.cs 100.00% 12 0 0 0 3 3 12 0 18
That/Strings/ThatString.IsOneOf.cs 90.48% 19 2 0 0 6 6 19 2 33
That/TimeOnlys/ThatNullableTimeOnly.IsOneOf.cs 100.00% 34 0 0 0 8 6 34 0 48
That/TimeOnlys/ThatTimeOnly.IsOneOf.cs 100.00% 29 0 0 0 6 6 29 0 41
That/TimeSpans/ThatNullableTimeSpan.IsOneOf.cs 100.00% 31 0 0 0 8 6 31 0 45
That/TimeSpans/ThatTimeSpan.IsOneOf.cs 100.00% 26 0 0 0 6 6 26 0 38

The final mutation score is 99.19%

Coverage Thresholds: high:80 low:60 break:0

aweXpect.Core

Details
File Score Killed Survived Timeout No Coverage Ignored Compile Errors Total Detected Total Undetected Total Mutants

The final mutation score is NaN%

Coverage Thresholds: high:80 low:60 break:0

@vbreuss vbreuss merged commit 9e84df7 into main Jun 30, 2025
13 checks passed
@vbreuss vbreuss deleted the topic/refactor-isoneof branch June 30, 2025 06:17
github-actions Bot added a commit that referenced this pull request Jun 30, 2025
…ected is empty in `IsOneOf` (2) (#671) by Valentin Breuß
github-actions Bot added a commit that referenced this pull request Jun 30, 2025
…ected is empty in `IsOneOf` (2) (#671) by Valentin Breuß
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jul 9, 2025

This is addressed in release v2.18.1.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working refactor A change or improvement without functional impact state: released The issue is released

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants