Here is a fully explained example of SmartTests usage:
We have a Bank system with 3 classes: Customer, BankAccount and Transaction.
Here are the tests (with NUnit, but could be MSTests or xUnit too):
To test Customer
class, I use two test classes:
using System; using Example01; using NUnit.Framework; using SmartTests.Assertions; using SmartTests.Criterias; using static SmartTests.SmartTest; namespace Example01Tests { [TestFixture] public class CustomerConstructorTests { [Test] public void HappyPath() { // Arrange Part var previous = new Customer( "Customer", "Address" ); // Act part: shows test case with combination of Case (per parameter) // The first parameter is the name of the parameter, the second is the tested criteria // Once you have your cases combinations, missing cases will appear automatically // (that will disappear when the corresponding tests are written) var customer = RunTest( Case( "name", ValidString.HasContent ) & Case( "address", ValidString.HasContent ), () => new Customer( "Ludovic Dubois", "Montreal" ) ); // Assert Part Assert.AreEqual( previous.Id + 1, customer.Id ); Assert.AreEqual( "Ludovic Dubois", customer.Name ); Assert.AreEqual( "Montreal", customer.Address ); Assert.IsEmpty( customer.Accounts ); } [Test] public void EmptyName() { // Act part: implement another case. // This case is an error, should be treated specifically (one error at a time) RunTest( Case( "name", ValidString.IsEmpty ) & Case( "address", ValidString.HasContent ), () => new Customer( "", "Montreal" ), // Assert Part // Ensure the ArgumentOutOfRangeException is thrown when the Customer is created // Better than Assert.Catch, as you can still have Smart Assertions after catching the failure SmartAssert.Throw<ArgumentOutOfRangeException>( "name" ) ); } [Test] public void NullName() { // Act part: implement another case. // This case is an error, should be treated specifically (one error at a time) RunTest( Case( "name", ValidString.IsNull ) & Case( "address", ValidString.HasContent ), () => new Customer( null, "Montreal" ), // Assert Part // Ensure the ArgumentNullException is thrown when the Customer is created // Better than Assert.Catch, as you can still have Smart Assertions after catching the failure SmartAssert.Throw<ArgumentNullException>( "name" ) ); } [Test] public void EmptyAddress() { // Act part: implement another case. // This case is an error, should be treated specifically (one error at a time) RunTest( Case( "name", ValidString.HasContent ) & Case( "address", ValidString.IsEmpty ), () => new Customer( "Ludovic Dubois", "" ), // Assert Part // Ensure the ArgumentOutOfRangeException is thrown when the Customer is created // Better than Assert.Catch, as you can still have Smart Assertions after catching the failure SmartAssert.Throw<ArgumentOutOfRangeException>( "address" ) ); } [Test] public void NullAddress() { // Act part: implement another case. // This case is an error, should be treated specifically (one error at a time) RunTest( Case( "name", ValidString.HasContent ) & Case( "address", ValidString.IsNull ), () => new Customer( "Ludovic Dubois", null ), // Assert Part // Ensure the ArgumentNullException is thrown when the Customer is created // Better than Assert.Catch, as you can still have Smart Assertions after catching the failure SmartAssert.Throw<ArgumentNullException>( "address" ) ); } } }
using System; using System.Linq; using Example01; using NUnit.Framework; using SmartTests.Assertions; using SmartTests.Criterias; using static SmartTests.SmartTest; namespace Example01Tests { [TestFixture] public class CustomerTests { private Customer _Customer; [SetUp] public void Setup() { _Customer = new Customer( "Ludovic Dubois", "Montreal" ); } // // Address PROPERTY TESTS // [Test] public void SetAddress_HappyPath() { RunTest( ValidString.HasContent, // Act // To Test Assignment, use Assign Assign( () => _Customer.Address, "Quebec" ), // Assert (and implicit Assume) // Before Act: Register to PropertyChangedEvent // After Act: Assert PropertyChanged raised when assignment is run, with "Address" as PropertyName SmartAssert.Raised_PropertyChanged(), // Before Act: Assume _Customer.Address != "Quebec" before assignment, keep track of all other public properties // AfterAct: _Customer.Address == "Quebec", all other public properties did not change SmartAssert.NotChangedExceptAct() ); } [Test] public void SetAddress_Empty() { RunTest( ValidString.IsEmpty, // Act // To Test Assignment, use Assign Assign( () => _Customer.Address, "" ), // Assert (and implicit Assume) // After Act: Assert ArgumentOutOfRangeException is thrown with ParamName == "value" and Message == "Address Cannot be empty not null!" (omitting Parameter Name: value) SmartAssert.Throw<ArgumentOutOfRangeException>( "value", "Address cannot be empty nor null!" ), // Before Act: Register to PropertyChangedEvent // After Act: Assert not PropertyChanged is raised SmartAssert.NotRaised_PropertyChanged(), // Before Act: keep track of all properties and fields // After Act: Assert no property nor field changed => the _Customer is not changed at all SmartAssert.NotChanged( NotChangedKind.All ) ); } [Test] public void SetAddress_Null() { RunTest( ValidString.IsNull, // Act // To Test Assignment, use Assign Assign( () => _Customer.Address, null ), // Assert (and implicit Assume) // After Act: Assert ArgumentOutOfRangeException is thrown with ParamName == "value" and Message == "Address Cannot be empty not null!" (omitting Parameter Name: value) SmartAssert.Throw<ArgumentNullException>( "value", "Address cannot be empty nor null!" ), // Before Act: Register to PropertyChangedEvent // After Act: Assert not PropertyChanged is raised SmartAssert.NotRaised_PropertyChanged(), // Before Act: keep track of all properties and fields // After Act: Assert no property nor field changed => the _Customer is not changed at all SmartAssert.NotChanged( NotChangedKind.All ) ); } // // CreateAccount METHOD TESTS // [Test] public void CreateAccount() { var previous = _Customer.CreateAccount(); var account = RunTest( // We always can call CreateAccount AnyValue.IsValid, // Act () => _Customer.CreateAccount(), // Assert (and implicit Assume) // Before act: Register to PropertyChangedEvent // After Act: Assert not PropertyChanged is raised SmartAssert.NotRaised_PropertyChanged(), // Before Act: Keep track of all properties and fields // After Act: Assert no property nor field changed => the _Customer is not changed at all SmartAssert.NotChanged( NotChangedKind.All ), // Before Act: Compute the expression _Customer.Account.Count + 1 and save its result // After Act: Assert current _Customer.Accounts.Count is the saved value (thus, that Count is one more than before) SmartAssert.Change( () => _Customer.Accounts.Count + 1 ) ); // Assert Assert.AreSame( account, _Customer.Accounts.Last() ); Assert.AreEqual( previous.Id + 1, account.Id ); Assert.AreSame( _Customer, account.Customer ); Assert.AreEqual( 0, account.Balance ); Assert.IsEmpty( account.Transactions ); } // // CloseAccount METHOD TEST // [Test] public void CloseAccount_HappyPath() { var account = _Customer.CreateAccount(); var result = RunTest( CollectionItem.IsInCollection, // Act () => _Customer.CloseAccount( account ), // Assert (and implicit Assume) // Before Act: Keep track of all properties and fields // After Act: Assert no property nor field changed => the _Customer is not changed at all SmartAssert.NotChanged( NotChangedKind.All ), // Before Act: Register to PropertyChangedEvent // After Act: Assert not PropertyChanged is raised SmartAssert.NotRaised_PropertyChanged(), // Before Act: Compute the expression _Customer.Account.Count - 1 and save its result // After Act: Assert current _Customer.Accounts.Count is the saved value (thus, that Count is one less than before) SmartAssert.Change( () => _Customer.Accounts.Count - 1 ) ); // Assert Assert.IsTrue( result ); } [Test] public void CloseAccount_NotOurAccount() { // Arrange var customer2 = new Customer( "You", "World" ); var account = customer2.CreateAccount(); var result = RunTest( CollectionItem.IsNotInCollection, // Act () => _Customer.CloseAccount( account ), // Assert (and implicit Assume) // Before Act: Keep track of all properties and fields // After Act: Assert no property nor field changed => the _Customer is not changed at all SmartAssert.NotChanged( NotChangedKind.All ), // Before Act: Register to PropertyChangedEvent // Assert Act: Ensure not PropertyChanged is raised SmartAssert.NotRaised_PropertyChanged(), // Before Act: Keep track of all public properties of _Customer.Accounts // After Act: Assert current _Customer.Accounts public properties are the saved value (thus, that _Customer.Accounts did not changed at all) SmartAssert.NotChanged( _Customer.Accounts ) ); // Assert Assert.IsFalse( result ); } [Test] public void CloseAccount_NoAccount() { RunTest( CollectionItem.IsNull, // Act () => _Customer.CloseAccount( null ), // Assert (and implicit Assume) // After: Assert ArgumentNullException is thrown for the right parameter (optional) with the right error message (optional) SmartAssert.Throw<ArgumentNullException>( "account", "account belonging to this customer is required" ), // Before Act: Register to PropertyChangedEvent // After Act: Assert not PropertyChanged is raised SmartAssert.NotRaised_PropertyChanged(), // Before Act: Keep track of all properties and fields // After Act: Assert no property nor field changed => the _Customer is not changed at all SmartAssert.NotChanged( NotChangedKind.All ), // Before Act: Keep track of all public properties of _Customer.Accounts // After Act: Assert current _Customer.Accounts public properties are the saved value (thus, that _Customer.Accounts did not changed at all) SmartAssert.NotChanged( _Customer.Accounts ) ); } } }
using System; using System.Linq; using Example01; using NUnit.Framework; using SmartTests.Assertions; using SmartTests.Criterias; using SmartTests.Ranges; using static SmartTests.SmartTest; namespace Example01Tests { [TestFixture] public class AccountTests { private Customer _Customer; private Account _Account; [SetUp] public void Setup() { _Customer = new Customer( "Ludovic", "Dubois" ); _Account = _Customer.CreateAccount(); } #region Deposit Tests [Test] public void Deposit_HappyPath() { var lowerNow = DateTime.Now; // We use a lambda expression to show equivalence class and to generate a random value within this equivalence class RunTest( Case( ( double amount ) => amount.Above( 0 ), out var value ), // Act () => _Account.Deposit( value ), // Assert (and implicit Assume) // Before Act: Keep tracks of all public properties // After Act: Assert all public properties have the same values, except Balance SmartAssert.NotChangedExcept( nameof(Account.Balance) ), // Before Act: Register to PropertyChangedEvent // After Act: Assert PropertyChanged is raised for Balance SmartAssert.Raised_PropertyChanged( _Account, nameof(Account.Balance) ), // Before Act: Compute _Account.Balance + value // After Act: Assert _Account.Balance is previous computed value SmartAssert.Change( () => _Account.Balance + value ), // Before Act: Compute _Account.Transactions.Count + 1 // After Act: Assert _Account.Transactions.Count is previous computed value SmartAssert.Change( () => _Account.Transactions.Count + 1 ) ); // Assert the added transaction reflects the Deposit var transaction = _Account.Transactions.Last(); Assert.AreEqual( _Account, transaction.Account ); Assert.AreEqual( value, transaction.Amount ); Assert.IsTrue( lowerNow <= transaction.Date && transaction.Date <= DateTime.Now ); Assert.AreEqual( TransactionKind.Deposit, transaction.Kind ); Assert.IsNull( transaction.SecondAccount ); } [Test] public void Deposit_Negative() { // We use a lambda expression to show equivalence class and to generate a random value within this equivalence class RunTest( Case( ( double amount ) => amount.BelowOrEqual( 0 ), out var value ), // Act () => _Account.Deposit( value ), // Assert (and implicit Assume) // After Act: Assert ArgumentOutOfRangeException is thrown with ParamName == "amount" SmartAssert.Throw<ArgumentOutOfRangeException>( "amount" ), // Before Act: Register to PropertyChangedEvent // After Act: Assert not PropertyChanged is raised SmartAssert.NotRaised_PropertyChanged(), // Before Act: keep track of all properties and fields // After Act: Assert no property nor field changed => the _Customer is not changed at all SmartAssert.NotChanged( NotChangedKind.All ) ); } #endregion #region Withdraw Tests [Test] public void Withdraw_HappyPath() { var lowerNow = DateTime.Now; _Account.Deposit( 1000 ); // We use a lambda expression to show equivalence class and to generate a random value within this equivalence class var success = RunTest( Case( ( double amount ) => amount.Range( 0, false, 1000, true ), out var value ), // Act () => _Account.Withdraw( value ), // Assert (and implicit Assume) // Before Act: Keep tracks of all public properties // After Act: Assert all public properties have the same values, except Balance SmartAssert.NotChangedExcept( nameof(Account.Balance) ), // Before Act: Register to PropertyChangedEvent // After Act: Assert PropertyChanged is raised for Balance SmartAssert.Raised_PropertyChanged( _Account, nameof(Account.Balance) ), // Before Act: Compute _Account.Balance - value // After Act: Assert _Account.Balance is previous computed value SmartAssert.Change( () => _Account.Balance - value ), // Before Act: Compute _Account.Transactions.Count + 1 // After Act: Assert _Account.Transactions.Count is previous computed value SmartAssert.Change( () => _Account.Transactions.Count + 1 ) ); Assert.IsTrue( success ); // Assert the added transaction reflects the Withdraw var transaction = _Account.Transactions.Last(); Assert.AreEqual( _Account, transaction.Account ); Assert.AreEqual( -value, transaction.Amount ); Assert.IsTrue( lowerNow <= transaction.Date && transaction.Date <= DateTime.Now ); Assert.AreEqual( TransactionKind.Withdraw, transaction.Kind ); Assert.IsNull( transaction.SecondAccount ); } [Test] public void Withdraw_Negative() { _Account.Deposit( 1000 ); // We use a lambda expression to show equivalence class and to generate a random value within this equivalence class RunTest( Case( ( double amount ) => amount.BelowOrEqual( 0 ), out var value ), // Act () => _Account.Withdraw( value ), // Assert (and implicit Assume) // After Act: Assert ArgumentOutOfRangeException is thrown with ParamName == "amount" SmartAssert.Throw<ArgumentOutOfRangeException>( "amount" ), // Before Act: Register to PropertyChangedEvent // After Act: Assert not PropertyChanged is raised SmartAssert.NotRaised_PropertyChanged(), // Before Act: Keep track of all properties and fields // After Act: Assert no property nor field changed => the _Customer is not changed at all SmartAssert.NotChanged( NotChangedKind.All ), // Before Act: Keep track of all public properties of _Account.Transactions // After Act: Assert not property of _Account.Transactions changed (especially Count) SmartAssert.NotChanged( _Account.Transactions ) ); } [Test] public void Withdraw_TooBig() { _Account.Deposit( 1000 ); // Assume Assert.AreEqual( 1000, _Account.Balance ); // We use a lambda expression to show equivalence class and to generate a random value within this equivalence class var success = RunTest( Case( ( double amount ) => amount.Above( 1000 ), out var value ), // Act () => _Account.Withdraw( value ), // Assert (and implicit Assume) // Before Act: Register to PropertyChangedEvent // After Act: Assert not PropertyChanged is raised SmartAssert.NotRaised_PropertyChanged(), // Before Act: keep track of all properties and fields // After Act: Assert no property nor field changed => the _Customer is not changed at all SmartAssert.NotChanged( NotChangedKind.All ), // Before Act: Keep track of all public properties of _Account.Transactions // After Act: Assert not property of _Account.Transactions changed (especially Count) SmartAssert.NotChanged( _Account.Transactions ) ); Assert.IsFalse( success ); } #endregion #region Transfer Tests [Test] public void Transfer_HappyPath() { var lowerNow = DateTime.Now; _Account.Deposit( 1000 ); var account2 = _Customer.CreateAccount(); // We use a lambda expression to show equivalence class and to generate a random value within this equivalence class var success = RunTest( Case( ( double amount ) => amount.Range( 0, false, 1000, true ), out var value ) & Case( "toAccount", ValidValue.IsValid ), // Act () => _Account.Transfer( value, account2 ), // Assert (and implicit Assume) // Before Act: Keep tracks of all public properties // After Act: Assert all public properties have the same values, except Balance SmartAssert.NotChangedExcept( nameof(Account.Balance) ), // Before Act: Keep tracks of all public properties of account2 // After Act: Assert all public properties of account2 have the same values, except Balance SmartAssert.NotChangedExcept( account2, nameof(Account.Balance) ), // Before Act: Register to PropertyChangedEvent // After Act: Assert PropertyChanged is raised for Balance SmartAssert.Raised_PropertyChanged( _Account, nameof(Account.Balance) ), // Before Act: Register to PropertyChangedEvent // After Act: Assert PropertyChanged is raised for Balance SmartAssert.Raised_PropertyChanged( account2, nameof(Account.Balance) ), // Before Act: Compute _Account.Balance - value // After Act: Assert _Account.Balance is previous computed value SmartAssert.Change( () => _Account.Balance - value ), // Before Act: Compute account2.Balance - value // After Act: Assert account2.Balance is previous computed value SmartAssert.Change( () => account2.Balance + value ), // Before Act: Compute _Account.Transactions.Count + 1 // After Act: Assert _Account.Transactions.Count is previous computed value SmartAssert.Change( () => _Account.Transactions.Count + 1 ), // Before Act: Compute account2.Transactions.Count + 1 // After Act: Assert account2.Transactions.Count is previous computed value SmartAssert.Change( () => account2.Transactions.Count + 1 ) ); Assert.IsTrue( success ); // Assert the added transaction reflects the Transfer var transaction = _Account.Transactions.Last(); Assert.AreEqual( _Account, transaction.Account ); Assert.AreEqual( -value, transaction.Amount ); Assert.IsTrue( lowerNow <= transaction.Date && transaction.Date <= DateTime.Now ); Assert.AreEqual( TransactionKind.Transfer, transaction.Kind ); Assert.AreEqual( account2, transaction.SecondAccount ); // Assert the added transaction reflects the Transfer var transaction2 = account2.Transactions.Last(); Assert.AreEqual( account2, transaction2.Account ); Assert.AreEqual( value, transaction2.Amount ); Assert.IsTrue( lowerNow <= transaction2.Date && transaction2.Date <= DateTime.Now ); Assert.AreEqual( TransactionKind.Transfer, transaction2.Kind ); Assert.AreEqual( _Account, transaction2.SecondAccount ); } [Test] public void Transfer_NegativeAmount() { _Account.Deposit( 1000 ); var account2 = _Customer.CreateAccount(); // We use a lambda expression to show equivalence class and to generate a random value within this equivalence class RunTest( Case( ( double amount ) => amount.BelowOrEqual( 0 ), out var value ) & Case( "toAccount", ValidValue.IsValid ), // Act () => _Account.Transfer( value, account2 ), // Assert (and implicit Assume) // After Act: Assert ArgumentOutOfRangeException is thrown with ParamName == "amount" SmartAssert.Throw<ArgumentOutOfRangeException>( "amount" ), // Before Act: Keep tracks of all public properties // After Act: Assert all public properties have the same values SmartAssert.NotChanged(), // Before Act: Keep tracks of all public properties // After Act: Assert all public properties have the same values SmartAssert.NotChanged( account2 ), // Before Act: Register to PropertyChangedEvent // After Act: Assert PropertyChanged is not raised SmartAssert.NotRaised_PropertyChanged(), // Before Act: Register to PropertyChangedEvent // After Act: Assert PropertyChanged is not raised SmartAssert.NotRaised_PropertyChanged( account2 ), // Before Act: Keep track of all public properties of _Account.Transactions // After Act: Assert not property of _Account.Transactions changed (especially Count) SmartAssert.NotChanged( _Account.Transactions ), // Before Act: Keep track of all public properties of account2.Transactions // After Act: Assert not property of account2.Transactions changed (especially Count) SmartAssert.NotChanged( account2.Transactions ) ); } [Test] public void Transfer_NoAccount2() { _Account.Deposit( 1000 ); // We use a lambda expression to show equivalence class and to generate a random value within this equivalence class RunTest( Case( ( double amount ) => amount.Range( 0, false, 1000, true ), out var value ) & Case( "toAccount", ValidValue.IsInvalid ), // Act () => _Account.Transfer( value, null ), // Assert (and implicit Assume) // After Act: Assert ArgumentNullException is thrown with ParamName == "toAccount" SmartAssert.Throw<ArgumentNullException>( "toAccount" ), // Before Act: Register to PropertyChangedEvent // After Act: Assert PropertyChanged is not raised SmartAssert.NotRaised_PropertyChanged(), // Before Act: Keep tracks of all public properties // After Act: Assert all public properties have the same values SmartAssert.NotChanged(), // Before Act: Keep track of all public properties of _Account.Transactions // After Act: Assert not property of _Account.Transactions changed (especially Count) SmartAssert.NotChanged( _Account.Transactions ) ); } [Test] public void Transfer_TooBig() { _Account.Deposit( 1000 ); var account2 = _Customer.CreateAccount(); // Assume Assert.AreEqual( 1000, _Account.Balance ); // We use a lambda expression to show equivalence class and to generate a random value within this equivalence class var success = RunTest( Case( ( double amount ) => amount.Above( 1000 ), out var value ) & Case( "toAccount", ValidValue.IsValid ), // Act () => _Account.Transfer( value, account2 ), // Assert (and implicit Assume) // Before Act: Keep tracks of all public properties // After Act: Assert all public properties have the same values SmartAssert.NotChanged(), // Before Act: Keep tracks of all public properties of account2 // After Act: Assert all public properties of account2 have the same values SmartAssert.NotChanged( account2 ), // Before Act: Register to PropertyChangedEvent // After Act: Assert no PropertyChanged is raised SmartAssert.NotRaised_PropertyChanged(), // Before Act: Register to PropertyChangedEvent // After Act: Assert no PropertyChanged is raised for account2 SmartAssert.NotRaised_PropertyChanged( account2 ), // Before Act: Keep track of all public properties of _Account.Transactions // After Act: Assert not property of _Account.Transactions changed (especially Count) SmartAssert.NotChanged( _Account.Transactions ), // Before Act: Keep track of all public properties of account2.Transactions // After Act: Assert not property of account2.Transactions changed (especially Count) SmartAssert.NotChanged( account2.Transactions ) ); Assert.IsFalse( success ); } #endregion } }