An interesting question came up on twitter a moment ago about creating entities for unit tests, what follows is the approach that I use to enable me to create entities with the minimum possible code yet the maximum possible flexibility.
I have two entities Account and User, the code for them is shown below.
public class Account : Entity<Account>
{
public Account( string accountName )
{
Name = accountName;
Users = new List<User>();
Questionnaires = new List<Questionnaire>();
}
// ReSharper disable UnusedMember.Local
private Account()
{
// Required by NHibernate
}
// ReSharper restore UnusedMember.Local
public string Name { get; private set; }
public User Owner { get; private set; }
public IList<User> Users { get; private set; }
public IList<Questionnaire> Questionnaires { get; private set; }
public User CreateUser( string username, string passphrase, string name, string emailAddress, string country, string timezone, bool asOwner )
{
var user = new User( username, passphrase, name, emailAddress, country, timezone, this );
Users.Add( user );
if( asOwner )
{
Owner = user;
}
return user;
}
public Questionnaire CreateQuestionnaire( string name )
{
Contract.Requires( () => name );
var questionnare = new Questionnaire( name, this );
Questionnaires.Add( questionnare );
return questionnare;
}
}
public class User : Entity<User>
{
public const int MaxAuthenticationRetryCount = 5;
public User( string username, string passphrase, string name, string emailAddress, string country, string timezone, Account account )
{
Contract.Requires( () => username );
Contract.Requires( () => passphrase );
Contract.Requires( () => name );
Contract.Requires( () => emailAddress );
Contract.Requires( () => country );
Contract.Requires( () => timezone );
Contract.Requires( () => account );
Username = username;
Passphrase = passphrase;
Name = name;
EmailAddress = emailAddress;
Country = country;
Timezone = timezone;
Account = account;
EmailAddressConfirmed = false;
}
// ReSharper disable UnusedMember.Local
private User()
{
// Required by NHibernate
}
// ReSharper restore UnusedMember.Local
public string Username { get; private set; }
public string Passphrase { get; private set; }
public string Name { get; private set; }
public string EmailAddress { get; private set; }
public string Country { get; private set; }
public string Timezone { get; private set; }
public Account Account { get; private set; }
public bool EmailAddressConfirmed { get; private set; }
public int AuthenticationRetryCount { get; private set; }
public void ConfirmEmailAddress()
{
EmailAddressConfirmed = true;
}
public bool Authenticate( string passphrase )
{
Contract.Requires( () => passphrase );
if (IsAccountLocked())
{
return false;
}
var isAuthenticated = String.Compare( Passphrase, passphrase, StringComparison.InvariantCulture ) == 0;
if (isAuthenticated)
{
AuthenticationRetryCount = 0;
}
else
{
AuthenticationRetryCount += 1;
}
return isAuthenticated;
}
public bool IsAccountLocked()
{
return AuthenticationRetryCount >= MaxAuthenticationRetryCount;
}
public void ResetPassphrase( string newPassphrase )
{
Contract.Requires( () => newPassphrase );
Passphrase = newPassphrase;
}
public void UnlockAccount()
{
AuthenticationRetryCount = 0;
}
}
To manage creation of these entities during testing I have a static factory class for each that manages creating the entities and optionally populating any properties ( often using reflection ). The source for these is as follows:
public static class AccountFactory
{
public static Account Create()
{
var account = new Account( "Account Name" );
UserFactory.Create( account, true );
return account;
}
}
public static class UserFactory
{
public static User Create()
{
var account = AccountFactory.Create();
return Create( account, true );
}
public static User Create( Account account, bool asOwner )
{
var user = account.CreateUser( "Username", "a valid passphrase", "Name", "email@address.com", "New Zealand", "GMT+12", asOwner );
return user;
}
public static User WithUsername( this User user, string username )
{
user.SetPropertyValue( u => u.Username, username );
return user;
}
public static User WithPassphrase( this User user, string passphrase )
{
user.SetPropertyValue( u => u.Passphrase, passphrase );
return user;
}
public static User WithEmailAddress( this User user, string emailAddress )
{
user.SetPropertyValue( u => u.EmailAddress, emailAddress );
return user;
}
public static User MimicFailedAuthenticationAttempts( this User user, int failedAttempts )
{
user.SetPropertyValue( u => u.AuthenticationRetryCount, failedAttempts );
return user;
}
public static User MimicLockout( this User user)
{
user.SetPropertyValue( u => u.AuthenticationRetryCount, User.MaxAuthenticationRetryCount );
return user;
}
}
The create methods return a valid entity initialised with a default set of values that can be used for testing any scenario you like. If you need to customise the data in the entities to fit a specific test scenario, the extension methods allow additional tweaking of the entity data ( often via reflection ).
This approach allows me to build entities quickly for testing purposes while ensuring that I don’t have to maintain a God class that manages building many different entities and their relationships, it is essentially convention over configuration for my entities.