Home > Domain Driven Design, Unit Testing > Using a Domain Model Simplifies Your Unit Testing

Using a Domain Model Simplifies Your Unit Testing

I’ve just had one of those AHA! moments – you know, when you are staring at code and you have a bunch of concepts running around the back of your mind and something just falls into place.

The code I happened to be staring at was a set of unit tests for my UserController, and the tests simply ensured the basic user management operations worked as they should (user is created, user is updated etc etc). For some reason while I was staring at this I was thinking about testing that the newly created users could log in successfully, but what that would really be testing was are the users created correctly?

This kind of thinking comes from my days before unit testing, domain objects and DRY, back then the code that created the user would be quite separate from the code that logged the user in and both of these actions would have a significant “plumbing” code in them meaning that if we changed something about the way the user was created, we could conceivably break the login process without knowing it (a lot of the applications I used to build were highly procedural / transaction script oriented), so I would have something like (warning: aircode):

public bool Login(string username, string password)
{
    DataRow userRow = UserDAO.GetUser( username );
    return userRow["Password"] == password;
}
… somewhere else in the app …

public void CreateUser(string username, string password, ... )
{
    UserDAO.CreateUser(username, password, ... );
}
So what would happen if the boss suddenly decreed that all passwords should be stored with hashes? I’d definately change the CreateUser code, but what if I didn’t write the Login code, would I know about it? Without unit tests, definitely not; with unit tests, maybe, depending on how much was mocked out (as ideally I wouldn’t be testing the data access code so my mock would return what I was expecting). Compare that to my current implementation – a Credentials class that contains all of the username / password / login attempts related details (and nothing about persistence):

    public class Credentials
    {
        public const int MaximumLoginAttempts = 3;

        private string _username;
        private readonly HashedPassword _password;
        private int _loginAttemptsRemaining;

        public string Username
        {
            get
            {
                return _username;
            }
        }

        public int LoginAttemptsRemaining
        {
            get
            {
                return _loginAttemptsRemaining;
            }
        }

        public bool IsLockedOut
        {
            get
            {
                return _loginAttemptsRemaining  MaximumLoginAttempts)
            {
                loginAttemptsRemaining = MaximumLoginAttempts;
            }

            _loginAttemptsRemaining = loginAttemptsRemaining;
        }

        public bool ValidatePassword(string password)
        {
            if (_loginAttemptsRemaining <= 0)
            {
                return false;
            }

            if (_password.IsSameAs(password))
            {
                _loginAttemptsRemaining = MaximumLoginAttempts;
                return true;
            }

            _loginAttemptsRemaining = _loginAttemptsRemaining - 1;
            return false;
        }
    }
This implementation already stores the data as a hashed password, but the original version didn’t (only for the first version where I was concentrating on getting the basic behaviours). Adding the ability to hash the password didn’t change the external interface in any way and therefore any existing tests I had didn’t require any changes – they just worked.

It is not so much that using a domain model directly simplifies your unit testing, it’s more that a domain model encourages you to implement separation of concerns and the single responsibility principle. SoC and SRP combine to significantly increase the testability of your application (as well as decreasing the fragility of the unit tests).

In my case, the true realisation was that because the responsibility for the persistence of the object was separate from the user object and the controller, I couldn’t break the login code by changing the persistence code or the user creation code (assuming I created a valid user object), and because I had previously tested the login code, I had already tested the login code under all of the scenarios that were applicable.

Building a domain model is definitely worth the effort!

Advertisement
  1. No comments yet.
  1. No trackbacks yet.

Leave a Reply

Fill in your details below or click an icon to log in:

Gravatar
WordPress.com Logo

Please log in to WordPress.com to post a comment to your blog.

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.