Press "Enter" to skip to content

Month: August 2016

Dependency Injection Design Pattern in C#

This post will demonstrate the Dependency Injection (DI) design pattern. Dependency Injection is also known as Dependency Inversion, or Inversion of Control (IoC).

 

Video version here:

 

What it is

Dependency Injection is a special use of the Strategy Pattern. If you aren’t familiar with it, please see this post on the Strategy Pattern first.

With the Strategy Pattern, you pass an object into a function. The function uses the passed-in object to perform some action. You can pass in different objects, to perform the action differently – as long as the object matches the specified interface.

With Dependency Injection, the action you want the Strategy object to perform is one where the function needs to use an external resource – like a database, the file system, a web service, etc.

The reason you would do this is to let you write automated tests for the function, without needing to access the external resource. That way, you don’t need to worry if the database/file system/web service is working the way your automated test needs it to work.

 

Examples

This example will use a “Player” class, for a game. It has a CreateNewPlayer function, which does three things:

  1. Raise an error if the player name parameter is an empty string.
  2. Raise an error if the player name already exists in the database.
  3. If there weren’t any errors, add the player to the database.

Notice that #2 and #3 need to access the database.

 

Non-Pattern Version

Player.cs

using System;
namespace Engine.DependencyInversionPattern.NonPatternVersion
{
    public class Player
    {
        public string Name { get; private set; }
        public int ExperiencePoints { get; private set; }
        public int Gold { get; private set; }
        public Player(string name, int experiencePoints, int gold)
        {
            Name = name;
            ExperiencePoints = experiencePoints;
            Gold = gold;
        }
        public static Player CreateNewPlayer(string name)
        {
            if(string.IsNullOrWhiteSpace(name))
            {
                throw new ArgumentException("Player name cannot be empty.");
            }
            // Create a Data Mapper object, to use to connect with the database
            PlayerDataMapper _playerDataMapper = new PlayerDataMapper();
            // Throw an exception if there is already a player with this name in the database.
            if(_playerDataMapper.PlayerNameExistsInDatabase(name))
            {
                throw new ArgumentException("Player name already exists.");
            }
            // Add the player to the database.
            _playerDataMapper.InsertNewPlayerIntoDatabase(name);
            return new Player(name, 0, 10);
        }
    }
}

The CreateNewPlayer function performs the first thing we need it to do – raise an error if the “name” parameter is empty.

Then, it creates a PlayerDataMapper object (see the code below), to do the next two things we need it to do – interact with the database.

 

PlayerDataMapper.cs

using System.Data;
using System.Data.SqlClient;
namespace Engine.DependencyInversionPattern.NonPatternVersion
{
    public class PlayerDataMapper
    {
        private readonly string _connectionString =
            "Data Source=(local);Initial Catalog=MyGame;Integrated Security=True";
        public bool PlayerNameExistsInDatabase(string name)
        {
            using(SqlConnection connection = new SqlConnection(_connectionString))
            {
                connection.Open();
                using(SqlCommand playersWithThisName = connection.CreateCommand())
                {
                    playersWithThisName.CommandType = CommandType.Text;
                    playersWithThisName.CommandText = "SELECT count(*) FROM Player WHERE 'Name' = @Name";
                    playersWithThisName.Parameters.AddWithValue("@Name", name);
                    // Get the number of player with this name 
                    var existingRowCount = (int)playersWithThisName.ExecuteScalar();
                    // Result is 0, if no player exists, or 1, if a player already exists
                    return existingRowCount > 0;
                }
            }
        }
        public void InsertNewPlayerIntoDatabase(string name)
        {
            using(SqlConnection connection = new SqlConnection(_connectionString))
            {
                connection.Open();
                using(SqlCommand playersWithThisName = connection.CreateCommand())
                {
                    playersWithThisName.CommandType = CommandType.Text;
                    playersWithThisName.CommandText = "INSERT INTO Player ([Name]) VALUES (@Name)";
                    playersWithThisName.Parameters.AddWithValue("@Name", name);
                    playersWithThisName.ExecuteNonQuery();
                }
            }
        }
    }
}

This has a problem, if we want to create automated tests for the CreateNewPlayer function.

For the tests to pass, we need to have a running database. We would want to create a test for when the player name exists in the database, and when it does not exist. So, we would need to add the record (or delete it) before running the different tests.

Also, if we have many tests that use the database, it will take a long time to run them and know if the tests pass – or not.

 

TestPlayer.cs

using System;
using Engine.DependencyInversionPattern.NonPatternVersion;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace TestEngine.DependencyInversionPattern.NonPatternVersion
{
    [TestClass]
    public class TestPlayer
    {
        // Test that we receive an exception, if the name parameter is an empty string.
        [TestMethod]
        [ExpectedException(typeof(ArgumentException))]
        public void Test_CreateNewPlayer_EmptyName()
        {
            Player player = Player.CreateNewPlayer("");
        }
        // Other tests would require a running database, which might have problems.
    }
}

So, in this unit test class, we can only test the failure condition for when the name parameter is an empty string. Any other test requires a working database, with specific data in it (or not in it).

 

Pattern Version

 

In order to create unit tests, which are tests that do not need external resources (such as a database, the file system, etc.), I’ll convert CreateNewPlayer to use Dependency Injection.

That way, I can pass in a “mock” PlayerDataMapper object for CreateNewPlayer to use. That mock object won’t need a database, and can be set up to return whatever results the unit tests need.

 

IPlayerDataMapper.cs

namespace Engine.DependencyInversionPattern.PatternVersion
{
    public interface IPlayerDataMapper
    {
        bool PlayerNameExistsInDatabase(string name);
        void InsertNewPlayerIntoDatabase(string name);
    }
}

This interface defines the functions that a PlayerDataMapper objects needs to perform – the same functions that were used in the non-pattern version.

 

PlayerDataMapper.cs

using System.Data;
using System.Data.SqlClient;
namespace Engine.DependencyInversionPattern.PatternVersion
{
    public class PlayerDataMapper : IPlayerDataMapper
    {
        private readonly string _connectionString =
            "Data Source=(local);Initial Catalog=MyGame;Integrated Security=True";
        public bool PlayerNameExistsInDatabase(string name)
        {
            using(SqlConnection connection = new SqlConnection(_connectionString))
            {
                connection.Open();
                using(SqlCommand playersWithThisName = connection.CreateCommand())
                {
                    playersWithThisName.CommandType = CommandType.Text;
                    playersWithThisName.CommandText = "SELECT count(*) FROM Player WHERE 'Name' = @Name";
                    playersWithThisName.Parameters.AddWithValue("@Name", name);
                    // Get the number of player with this name 
                    var existingRowCount = (int)playersWithThisName.ExecuteScalar();
                    // Result is 0, if no player exists, or 1, if a player already exists
                    return existingRowCount > 0;
                }
            }
        }
        public void InsertNewPlayerIntoDatabase(string name)
        {
            using(SqlConnection connection = new SqlConnection(_connectionString))
            {
                connection.Open();
                using(SqlCommand playersWithThisName = connection.CreateCommand())
                {
                    playersWithThisName.CommandType = CommandType.Text;
                    playersWithThisName.CommandText = "INSERT INTO Player ([Name]) VALUES (@Name)";
                    playersWithThisName.Parameters.AddWithValue("@Name", name);
                    playersWithThisName.ExecuteNonQuery();
                }
            }
        }
    }
}

This class is the same as in the non-pattern version, except we declare that it implements IPlayerDataMapper.

 

Player.cs

using System;
namespace Engine.DependencyInversionPattern.PatternVersion
{
    public class Player
    {
        public string Name { get; private set; }
        public int ExperiencePoints { get; private set; }
        public int Gold { get; private set; }
        public Player(string name, int experiencePoints, int gold)
        {
            Name = name;
            ExperiencePoints = experiencePoints;
            Gold = gold;
        }
        public static Player CreateNewPlayer(string name, IPlayerDataMapper playerDataMapper = null)
        {
            // If a PlayerDataMapper was not passed in, use a real one.
            // This allows us to pass in a "mock" PlayerDataMapper (for testing),
            // but use a real PlayerDataMapper, when running the program.
            if(playerDataMapper == null)
            {
                playerDataMapper = new PlayerDataMapper();
            }
            if(string.IsNullOrWhiteSpace(name))
            {
                throw new ArgumentException("Player name cannot be empty.");
            }
            // Throw an exception if there is already a player with this name in the database.
            if(playerDataMapper.PlayerNameExistsInDatabase(name))
            {
                throw new ArgumentException("Player name already exists.");
            }
            // Add the player to the database.
            playerDataMapper.InsertNewPlayerIntoDatabase(name);
            return new Player(name, 0, 10);
        }
    }
}

This class is similar to the non-pattern version. However, the CreateNewPlayer function has a second parameter – an object that implements IPlayerDataMapper.

NOTE: The “= null” after the parameter (if you aren’t familiar with that) means the parameter is optional. If the function is called with one parameter, it will pretend it received a “null” for the “playerDataMapper” parameter.

If the playerDataMapper parameter is null, the function will create a new (non-mock) PlayerDataMapper object to use. If an object is passed in for the parameter, the function will use that object.

This is the dependency injection.

Instead of CreateNewPlayer always using its own PlayerDataMapper object, we can pass in (inject) an object that implements IPlayerDataMapper.

 

MockPlayerDataMapper.cs

using Engine.DependencyInversionPattern.PatternVersion;
namespace TestEngine.DependencyInversionPattern.PatternVersion
{
    public class MockPlayerDataMapper : IPlayerDataMapper
    {
        // This property holds the value for PlayerNameExistsInDatabase to return.
        // This lets us "mock" the results that we would receive from a real database.
        public bool ResultToReturn { get; set; }
        // These functions implement the IPlayerDataMapper interface.
        public bool PlayerNameExistsInDatabase(string name)
        {
            // This is whatever answer we need, for the current unit test.
            return ResultToReturn;
        }
        public void InsertNewPlayerIntoDatabase(string name)
        {
        }
    }
}

This class implements IPlayerDataMapper. I’ll can create an instance of it and pass that into the CreateNewPlayer function.

However, the methods in this “mock” class do not touch the database. Plus, we can configure the PlayerNameExistsInDatabase() function to return a true or false – whichever result the unit test needs.

NOTE: For real unit tests, I would use a mocking library (such as Moq), instead of manually creating mock classes. That is much faster, and simpler, than manually creating mock objects.

 

TestPlayer.cs

using System;
using Engine.DependencyInversionPattern.PatternVersion;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace TestEngine.DependencyInversionPattern.PatternVersion
{
    [TestClass]
    public class TestPlayer
    {
        // Should receive an exception, because the name is an empty string.
        [TestMethod]
        [ExpectedException(typeof(ArgumentException))]
        public void Test_CreateNewPlayer_EmptyName()
        {
            Player player = Player.CreateNewPlayer("");
        }
        // Should still receive an exception, because the name is an empty string.
        [TestMethod]
        [ExpectedException(typeof(ArgumentException))]
        public void Test_CreateNewPlayer_EmptyName_MockedDataMapper()
        {
            MockPlayerDataMapper mockPlayerDataMapper = new MockPlayerDataMapper();
            Player player = Player.CreateNewPlayer("", mockPlayerDataMapper);
        }
        // Should receive an exception, because we set the mock PlayerDataMapper 
        // to say the player name already exists in the database.
        [TestMethod]
        [ExpectedException(typeof(ArgumentException))]
        public void Test_CreateNewPlayer_AlreadyExistsInDatabase()
        {
            MockPlayerDataMapper mockPlayerDataMapper = new MockPlayerDataMapper();
            mockPlayerDataMapper.ResultToReturn = true;
            Player player = Player.CreateNewPlayer("Test", mockPlayerDataMapper);
        }
        // Should succeed, because we set the mock PlayerDataMapper 
        // to say the player name does not already exist in the database.
        // Also, when it calls the mock InsertNewPlayerIntoDatabase,
        // the mock mapper will not need a database running.
        [TestMethod]
        public void Test_CreateNewPlayer_DoesNotAlreadyExistInDatabase()
        {
            MockPlayerDataMapper mockPlayerDataMapper = new MockPlayerDataMapper();
            mockPlayerDataMapper.ResultToReturn = false;
            Player player = Player.CreateNewPlayer("Test", mockPlayerDataMapper);
            Assert.AreEqual("Test", player.Name);
            Assert.AreEqual(0, player.ExperiencePoints);
            Assert.AreEqual(10, player.Gold);
        }
    }
}

Now, I can create unit tests for all possible paths through the function.

I would still want to test that the code works with a database. However, this is a good way to test the function – especially if there were more non-database validations in it.

 

Where I’ve found it useful

I’ve used this to create many automated unit tests.

With a large program, it would take weeks, or months, to manually test all possible paths through the code. With automated tests, that use mock objects, you can usually test every possible path in a few minutes.

I’ve mostly used this pattern to let me create automated tests for my code that uses external resources (the file system, a database, a web service, etc.)

If you want to use dependency injection with a large application, consider using an Inversion of Control (IOC) library, such as Autofac, Castle Windsor, Ninject, or StructureMap.

These libraries can make it easier to do dependency injection. However, they do require some configuration, or registration, of your application’s classes. It’s usually easy to do – although, if you haven’t added any new classes to your project lately, you need to remember how/where to do the configuration.

They can also cause a problem if you use any static analysis (code quality) tools on your source code. They may not recognize that your objects are instantiated by the IoC library, and think you have unused classes that should be removed.

 

All my design pattern lessons

Source code for my design pattern lessons

 

10 Comments