// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Text; using System.Reactive.Disposables; using System.Reactive.Linq; #if NUNIT using NUnit.Framework; #elif WINDOWS8 using Microsoft.VisualStudio.TestPlatform.UnitTestFramework; #else using Microsoft.VisualStudio.TestTools.UnitTesting; #endif namespace Microsoft.Reactive.Testing { /// /// Helper class to write asserts in unit tests for applications and libraries built using Reactive Extensions. /// public static class ReactiveAssert { static string Message(IEnumerable actual, IEnumerable expected) { var sb = new StringBuilder(); sb.AppendLine(); sb.Append("Expected: ["); sb.Append(string.Join(", ", expected.Select(x => x.ToString()).ToArray())); sb.Append("]"); sb.AppendLine(); sb.Append("Actual..: ["); sb.Append(string.Join(", ", actual.Select(x => x.ToString()).ToArray())); sb.Append("]"); sb.AppendLine(); return sb.ToString(); } /// /// Asserts that both enumerable sequences have equal length and equal elements. /// /// The type of the elements in the sequence. /// Expected sequence. /// Actual sequence to compare against the expected one. /// or is null. public static void AreElementsEqual(IEnumerable expected, IEnumerable actual) { if (expected == null) throw new ArgumentNullException("expected"); if (actual == null) throw new ArgumentNullException("actual"); if (!expected.SequenceEqual(actual)) Assert.Fail(Message(actual, expected)); } /// /// Asserts that both enumerable sequences have equal length and equal elements. /// /// The type of the elements in the sequence. /// Expected sequence. /// Actual sequence to compare against the expected one. /// Error message for assert failure. /// or is null. public static void AreElementsEqual(IEnumerable expected, IEnumerable actual, string message) { if (expected == null) throw new ArgumentNullException("expected"); if (actual == null) throw new ArgumentNullException("actual"); if (!expected.SequenceEqual(actual)) Assert.Fail(message); } /// /// Asserts that both observable sequences have equal length and equal notifications. /// /// The type of the elements in the sequence. /// Expected sequence. /// Actual sequence to compare against the expected one. /// or is null. public static void AreElementsEqual(IObservable expected, IObservable actual) { if (expected == null) throw new ArgumentNullException("expected"); if (actual == null) throw new ArgumentNullException("actual"); AreElementsEqual(expected.Materialize().ToEnumerable(), actual.Materialize().ToEnumerable()); } /// /// Asserts that both observable sequences have equal length and equal elements. /// /// The type of the elements in the sequence. /// Expected sequence. /// Actual sequence to compare against the expected one. /// Error message for assert failure. /// or is null. public static void AreElementsEqual(IObservable expected, IObservable actual, string message) { if (expected == null) throw new ArgumentNullException("expected"); if (actual == null) throw new ArgumentNullException("actual"); AreElementsEqual(expected.Materialize().ToEnumerable(), actual.Materialize().ToEnumerable(), message); } /// /// Asserts that the given action throws an exception of the type specified in the generic parameter, or a subtype thereof. /// /// Type of the exception to check for. /// Action to run. /// is null. public static void Throws(Action action) where TException : Exception { if (action == null) throw new ArgumentNullException("action"); var failed = false; try { action(); failed = true; } catch (TException) { } catch (Exception ex) { Assert.Fail(string.Format(CultureInfo.CurrentCulture, "Expected {0} threw {1}.\r\n\r\nStack trace:\r\n{2}", typeof(TException).Name, ex.GetType().Name, ex.StackTrace)); } if (failed) Assert.Fail(string.Format(CultureInfo.CurrentCulture, "Expected {0}.", typeof(TException).Name)); } /// /// Asserts that the given action throws an exception of the type specified in the generic parameter, or a subtype thereof. /// /// Type of the exception to check for. /// Action to run. /// Error message for assert failure. /// is null. public static void Throws(Action action, string message) where TException : Exception { if (action == null) throw new ArgumentNullException("action"); var failed = false; try { action(); failed = true; } catch (TException) { } catch { Assert.Fail(message); } if (failed) Assert.Fail(message); } /// /// Asserts that the given action throws the specified exception. /// /// Type of the exception to check for. /// Exception to assert being thrown. /// Action to run. /// is null. public static void Throws(TException exception, Action action) where TException : Exception { if (action == null) throw new ArgumentNullException("action"); var failed = false; try { action(); failed = true; } catch (TException ex) { Assert.AreSame(exception, ex); } catch (Exception ex) { Assert.Fail(string.Format(CultureInfo.CurrentCulture, "Expected {0} threw {1}.\r\n\r\nStack trace:\r\n{2}", typeof(TException).Name, ex.GetType().Name, ex.StackTrace)); } if (failed) Assert.Fail(string.Format(CultureInfo.CurrentCulture, "Expected {0}.", typeof(TException).Name)); } /// /// Asserts that the given action throws the specified exception. /// /// Type of the exception to check for. /// Exception to assert being thrown. /// Action to run. /// Error message for assert failure. /// is null. public static void Throws(TException exception, Action action, string message) where TException : Exception { if (action == null) throw new ArgumentNullException("action"); var failed = false; try { action(); failed = true; } catch (TException ex) { Assert.AreSame(exception, ex); } catch { Assert.Fail(message); } if (failed) Assert.Fail(message); } /// /// Asserts that both enumerable sequences have equal length and equal elements. /// /// The type of the elements in the sequence. /// Actual sequence to compare against the expected one. /// Expected sequence. /// or is null. public static void AssertEqual(this IEnumerable actual, IEnumerable expected) { if (actual == null) throw new ArgumentNullException("actual"); if (expected == null) throw new ArgumentNullException("expected"); ReactiveAssert.AreElementsEqual(expected, actual); } /// /// Asserts the enumerable sequence has the expected elements. /// /// The type of the elements in the sequence. /// Actual sequence to compare against the expected elements. /// Expected elements. /// or is null. public static void AssertEqual(this IEnumerable actual, params T[] expected) { if (actual == null) throw new ArgumentNullException("actual"); if (expected == null) throw new ArgumentNullException("expected"); ReactiveAssert.AreElementsEqual(expected, actual); } /// /// Asserts that both observable sequences have equal length and equal notifications. /// /// The type of the elements in the sequence. /// Actual sequence to compare against the expected one. /// Expected sequence. /// or is null. public static void AssertEqual(this IObservable actual, IObservable expected) { if (actual == null) throw new ArgumentNullException("actual"); if (expected == null) throw new ArgumentNullException("expected"); ReactiveAssert.AreElementsEqual(expected, actual); } } }