Skip to content
Permalink
master
Switch branches/tags
Go to file
 
 
Cannot retrieve contributors at this time
702 lines (588 sloc) 21.6 KB
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using FluentAssertions;
using FluentAssertions.Common;
using FluentAssertions.Execution;
using Xunit;
using Xunit.Sdk;
#pragma warning disable RCS1110 // Declare type inside namespace.
public class AssertionScopeSpecsWithoutNamespace
#pragma warning restore RCS1110 // Declare type inside namespace.
{
#if !NETCOREAPP1_1 && !NETSTANDARD1_3 && !NETSTANDARD1_6 && !NETSTANDARD2_0
[Fact]
public void This_class_should_not_be_inside_a_namespace()
{
// Arrange
Type type = typeof(AssertionScopeSpecsWithoutNamespace);
// Act / Assert
type.Assembly.Should().DefineType(null, type.Name, "this class should not be inside a namespace");
}
#endif
[Fact]
public void When_the_test_method_is_not_inside_a_namespace_it_should_not_throw_a_NullReferenceException()
{
// Act
Action act = () => 1.Should().Be(2, "we don't want a NullReferenceException");
// Assert
act.Should().ThrowExactly<XunitException>()
.WithMessage("*we don't want a NullReferenceException*");
}
}
namespace FluentAssertions.Specs
{
public class AssertionScopeSpecs
{
[Fact]
public void When_disposed_it_should_throw_any_failures()
{
// Arrange
var scope = new AssertionScope();
AssertionScope.Current.FailWith("Failure1");
// Act
Action act = scope.Dispose;
// Assert
try
{
act();
}
catch (Exception exception)
{
exception.Message.Should().StartWith("Failure1");
}
}
[Fact]
public void When_disposed_it_should_throw_any_failures_and_properly_format_using_args()
{
// Arrange
var scope = new AssertionScope();
AssertionScope.Current.FailWith("Failure{0}", 1);
// Act
Action act = scope.Dispose;
// Assert
try
{
act();
}
catch (Exception exception)
{
exception.Message.Should().StartWith("Failure1");
}
}
[Fact]
public void When_lazy_version_is_not_disposed_it_should_not_execute_fail_reason_function()
{
// Arrange
var scope = new AssertionScope();
bool failReasonCalled = false;
AssertionScope.Current
.ForCondition(true)
.FailWith(() =>
{
failReasonCalled = true;
return new FailReason("Failure");
});
// Act
Action act = scope.Dispose;
// Assert
act();
failReasonCalled.Should().BeFalse(" fail reason function cannot be called for scope that successful");
}
[Fact]
public void When_lazy_version_is_disposed_it_should_throw_any_failures_and_properly_format_using_args()
{
// Arrange
var scope = new AssertionScope();
AssertionScope.Current.FailWith(() => new FailReason("Failure{0}", 1));
// Act
Action act = scope.Dispose;
// Assert
try
{
act();
}
catch (Exception exception)
{
exception.Message.Should().StartWith("Failure1");
}
}
[Fact]
public void When_multiple_scopes_are_nested_it_should_throw_all_failures_from_the_outer_scope()
{
// Arrange
var scope = new AssertionScope();
AssertionScope.Current.FailWith("Failure1");
using (var nestedScope = new AssertionScope())
{
nestedScope.FailWith("Failure2");
using (var deeplyNestedScope = new AssertionScope())
{
deeplyNestedScope.FailWith("Failure3");
}
}
// Act
Action act = scope.Dispose;
// Assert
try
{
act();
}
catch (Exception exception)
{
exception.Message.Should().Contain("Failure1");
exception.Message.Should().Contain("Failure2");
exception.Message.Should().Contain("Failure3");
}
}
[Fact]
public void When_a_nested_scope_is_discarded_its_failures_should_also_be_discarded()
{
// Arrange
var scope = new AssertionScope();
AssertionScope.Current.FailWith("Failure1");
using (var nestedScope = new AssertionScope())
{
nestedScope.FailWith("Failure2");
using (var deeplyNestedScope = new AssertionScope())
{
deeplyNestedScope.FailWith("Failure3");
deeplyNestedScope.Discard();
}
}
// Act
Action act = scope.Dispose;
// Assert
try
{
act();
}
catch (Exception exception)
{
exception.Message.Should().Contain("Failure1");
exception.Message.Should().Contain("Failure2");
exception.Message.Should().NotContain("Failure3");
}
}
[Fact]
public void When_the_same_failure_is_handled_twice_or_more_it_should_still_report_it_once()
{
// Arrange
var scope = new AssertionScope();
AssertionScope.Current.FailWith("Failure");
AssertionScope.Current.FailWith("Failure");
using (var nestedScope = new AssertionScope())
{
nestedScope.FailWith("Failure");
nestedScope.FailWith("Failure");
}
// Act
Action act = scope.Dispose;
// Assert
try
{
act();
}
catch (Exception exception)
{
int matches = new Regex(".*Failure.*").Matches(exception.Message).Count;
matches.Should().Be(4);
}
}
[Fact]
public void When_an_assertion_fails_in_a_named_scope_it_should_use_the_name_as_the_assertion_context()
{
// Act
Action act = () =>
{
using (new AssertionScope("foo"))
{
new[] { 1, 2, 3 }.Should().Equal(3, 2, 1);
}
};
// Assert
act.Should().Throw<XunitException>()
.WithMessage("Expected foo to be equal to*");
}
[Fact]
public void When_an_assertion_fails_on_ContainKey_succeeding_message_should_be_included()
{
// Act
Action act = () =>
{
using (new AssertionScope())
{
var values = new Dictionary<int, int>();
values.Should().ContainKey(0);
values.Should().ContainKey(1);
}
};
// Assert
act.Should().Throw<XunitException>()
.WithMessage("Expected*to contain key 0*Expected*to contain key 1*");
}
[Fact]
public void When_an_assertion_fails_on_ContainSingle_succeeding_message_should_be_included()
{
// Act
Action act = () =>
{
using (new AssertionScope())
{
var values = new List<int>();
values.Should().ContainSingle();
values.Should().ContainSingle();
}
};
// Assert
act.Should().Throw<XunitException>()
.WithMessage("Expected*to contain a single item, but the collection is empty*" +
"Expected*to contain a single item, but the collection is empty*");
}
[Fact]
public void When_an_assertion_fails_on_BeOfType_succeeding_message_should_be_included()
{
// Act
Action act = () =>
{
using (new AssertionScope())
{
var item = string.Empty;
item.Should().BeOfType<int>();
item.Should().BeOfType<long>();
}
};
// Assert
act.Should().Throw<XunitException>()
.WithMessage(
"Expected type to be System.Int32, but found System.String.*" +
"Expected type to be System.Int64, but found System.String.");
}
[Fact]
public void When_an_assertion_fails_on_BeAssignableTo_succeeding_message_should_be_included()
{
// Act
Action act = () =>
{
using (new AssertionScope())
{
var item = string.Empty;
item.Should().BeAssignableTo<int>();
item.Should().BeAssignableTo<long>();
}
};
// Assert
act.Should().Throw<XunitException>()
.WithMessage(
"Expected * to be assignable to System.Int32, but System.String is not.*" +
"Expected * to be assignable to System.Int64, but System.String is not.");
}
[Fact]
public void When_parentheses_are_used_in_the_because_arguments_it_should_render_them_correctly()
{
// Act
Action act = () => 1.Should().Be(2, "can't use these in becauseArgs: {0} {1}", "{", "}");
// Assert
act.Should().Throw<XunitException>()
.WithMessage("*because can't use these in becauseArgs: { }*");
}
[Fact]
public void When_becauseArgs_is_null_it_should_render_reason_correctly()
{
// Act
object[] becauseArgs = null;
Action act = () => 1.Should().Be(2, "it should still work", becauseArgs);
// Assert
act.Should().Throw<XunitException>()
.WithMessage("*it should still work*");
}
[Fact]
public void When_invalid_format_is_used_in_because_parameter_without_becauseArgs_it_should_still_render_reason_correctly()
{
// Act
Action act = () => 1.Should().Be(2, "use of {} is okay if there are no because parameters");
// Assert
act.Should().Throw<XunitException>()
.WithMessage("*because use of {} is okay if there are no because parameters*");
}
[Fact]
public void When_invalid_format_is_used_in_because_parameter_along_with_becauseArgs_it_should_render_default_text()
{
// Act
Action act = () => 1.Should().Be(2, "use of {} is considered invalid in because parameter with becauseArgs", "additional becauseArgs parameter");
// Assert
act.Should().Throw<XunitException>()
.WithMessage("*because message 'use of {} is considered invalid in because parameter with becauseArgs' could not be formatted with string.Format*");
}
[Fact]
public void When_an_assertion_fails_in_a_scope_with_braces_it_should_use_the_name_as_the_assertion_context()
{
// Act
Action act = () =>
{
using (new AssertionScope("{}"))
{
default(Array).Should().Equal(3, 2, 1);
}
};
// Assert
act.Should().Throw<XunitException>()
.WithMessage("Expected {} to be equal to*");
}
[Fact]
public void When_parentheses_are_used_in_literal_values_it_should_render_them_correctly()
{
// Act
Action act = () => "{foo}".Should().Be("{bar}");
// Assert
act.Should().Throw<XunitException>()
.WithMessage("Expected string to be \"{bar}\", but \"{foo}\" differs near*");
}
[Fact]
public void When_message_contains_double_braces_they_should_not_be_replaced_with_context()
{
// Arrange
var scope = new AssertionScope();
AssertionScope.Current.FailWith("{{empty}}");
// Act
Action act = scope.Dispose;
// Assert
act.Should().ThrowExactly<XunitException>()
.WithMessage("*empty*");
}
[InlineData("\r")]
[InlineData("\\r")]
[InlineData("\\\r")]
[InlineData("\\\\r")]
[InlineData("\\\\\r")]
[Theory]
public void When_message_contains_backslash_followed_by_r_is_should_format_correctly(string str)
{
// Arrange
var scope = new AssertionScope();
AssertionScope.Current.FailWith(str);
// Act
Action act = scope.Dispose;
// Assert
act.Should().ThrowExactly<XunitException>()
.WithMessage(str);
}
[InlineData("\r")]
[InlineData("\\r")]
[InlineData("\\\r")]
[InlineData("\\\\r")]
[InlineData("\\\\\r")]
[Theory]
public void When_message_argument_contains_backslash_followed_by_r_is_should_format_correctly(string str)
{
// Arrange
var scope = new AssertionScope();
AssertionScope.Current.FailWith("\\{0}\\A", str);
// Act
Action act = scope.Dispose;
// Assert
act.Should().ThrowExactly<XunitException>()
.WithMessage("\\\"" + str + "\"\\A*");
}
[InlineData("\n")]
[InlineData("\\n")]
[InlineData("\\\n")]
[InlineData("\\\\n")]
[InlineData("\\\\\n")]
[Theory]
public void When_message_contains_backslash_followed_by_n_is_should_format_correctly(string str)
{
// Arrange
var scope = new AssertionScope();
AssertionScope.Current.FailWith(str);
// Act
Action act = scope.Dispose;
// Assert
act.Should().ThrowExactly<XunitException>()
.WithMessage(str);
}
[InlineData("\n")]
[InlineData("\\n")]
[InlineData("\\\n")]
[InlineData("\\\\n")]
[InlineData("\\\\\n")]
[Theory]
public void When_message_argument_contains_backslash_followed_by_n_is_should_format_correctly(string str)
{
// Arrange
var scope = new AssertionScope();
AssertionScope.Current.FailWith("\\{0}\\A", str);
// Act
Action act = scope.Dispose;
// Assert
act.Should().ThrowExactly<XunitException>()
.WithMessage("\\\"" + str + "\"\\A*");
}
[Fact]
public void When_subject_has_trailing_backslash_the_failure_message_should_contain_the_trailing_backslash()
{
// Arrange / Act
Action act = () => "A\\".Should().Be("A");
// Assert
act.Should().Throw<XunitException>()
.WithMessage(@"* near ""\"" *", "trailing backslashes should not be removed from failure message");
}
[Fact]
public void When_expectation_has_trailing_backslash_the_failure_message_should_contain_the_trailing_backslash()
{
// Arrange / Act
Action act = () => "A".Should().Be("A\\");
// Assert
act.Should().Throw<XunitException>()
.WithMessage(@"* to be ""A\"" *", "trailing backslashes should not be removed from failure message");
}
[Fact]
public void When_message_starts_with_single_braces_they_should_be_replaced_with_context()
{
// Arrange
var scope = new AssertionScope();
scope.AddReportable("MyKey", "MyValue");
AssertionScope.Current.FailWith("{MyKey}");
// Act
Action act = scope.Dispose;
// Assert
act.Should().ThrowExactly<XunitException>()
.WithMessage("*MyValue*");
}
[Fact]
public void When_message_starts_with_two_single_braces_they_should_be_replaced_with_context()
{
// Arrange
var scope = new AssertionScope();
scope.AddReportable("SomeKey", "SomeValue");
scope.AddReportable("AnotherKey", "AnotherValue");
AssertionScope.Current.FailWith("{SomeKey}{AnotherKey}");
// Act
Action act = scope.Dispose;
// Assert
act.Should().ThrowExactly<XunitException>()
.WithMessage("*SomeValue*AnotherValue*");
}
[Fact]
public async Task When_using_AssertionScope_across_thread_boundaries_it_should_work()
{
using (var semaphore = new SemaphoreSlim(0, 1))
{
await Task.WhenAll(SemaphoreYieldAndWait(semaphore), SemaphoreYieldAndRelease(semaphore));
}
}
private static async Task SemaphoreYieldAndWait(SemaphoreSlim semaphore)
{
await Task.Yield();
var scope = new AssertionScope();
await semaphore.WaitAsync();
scope.Should().BeSameAs(AssertionScope.Current);
}
private static async Task SemaphoreYieldAndRelease(SemaphoreSlim semaphore)
{
await Task.Yield();
var scope = new AssertionScope();
semaphore.Release();
scope.Should().BeSameAs(AssertionScope.Current);
}
[Fact]
public void When_custom_strategy_used_respect_its_behavior()
{
// Arrange
var scope = new AssertionScope(new FailWithStupidMessageAssertionStrategy());
// Act
Action act = () => scope.FailWith("Failure 1");
// Assert
act.Should().ThrowExactly<XunitException>()
.WithMessage("Good luck with understanding what's going on!");
}
[Fact]
public void When_custom_strategy_is_null_it_should_throw()
{
// Arrange
IAssertionStrategy strategy = null;
// Arrange / Act
Action act = () => new AssertionScope(strategy);
// Assert
act.Should().ThrowExactly<ArgumentNullException>()
.Which.ParamName.Should().Be("assertionStrategy");
}
[Fact]
public void When_using_a_custom_strategy_it_should_include_failure_messages_of_all_failing_assertions()
{
// Arrange
var scope = new AssertionScope(new CustomAssertionStrategy());
false.Should().BeTrue();
true.Should().BeFalse();
// Act
Action act = scope.Dispose;
// Assert
act.Should().ThrowExactly<XunitException>()
.WithMessage("*but found false*but found true*");
}
#if NET45
[Serializable]
#endif
public class CustomAssertionStrategy : IAssertionStrategy
{
private readonly List<string> failureMessages = new List<string>();
/// <summary>
/// Returns the messages for the assertion failures that happened until now.
/// </summary>
public IEnumerable<string> FailureMessages => failureMessages;
/// <summary>
/// Discards and returns the failure messages that happened up to now.
/// </summary>
public IEnumerable<string> DiscardFailures()
{
var discardedFailures = failureMessages.ToArray();
failureMessages.Clear();
return discardedFailures;
}
/// <summary>
/// Will throw a combined exception for any failures have been collected since <see cref="StartCollecting"/> was called.
/// </summary>
public void ThrowIfAny(IDictionary<string, object> context)
{
if (failureMessages.Any())
{
var builder = new StringBuilder();
builder.AppendLine(string.Join(Environment.NewLine, failureMessages));
if (context.Any())
{
foreach (KeyValuePair<string, object> pair in context)
{
builder.AppendFormat("\nWith {0}:\n{1}", pair.Key, pair.Value);
}
}
Services.ThrowException(builder.ToString());
}
}
/// <summary>
/// Instructs the strategy to handle a assertion failure.
/// </summary>
public void HandleFailure(string message)
{
failureMessages.Add(message);
}
}
#if NET45
[Serializable]
#endif
internal class FailWithStupidMessageAssertionStrategy : IAssertionStrategy
{
public IEnumerable<string> FailureMessages => new string[0];
public void HandleFailure(string message) =>
Services.ThrowException("Good luck with understanding what's going on!");
public IEnumerable<string> DiscardFailures() => new string[0];
public void ThrowIfAny(IDictionary<string, object> context)
{
// do nothing
}
}
}
}