Dependency Injection With Test Driven Development

With unit tests you can check that your code behaviours just as you expect it to. When writing your unit tests you shouldn't need to worry about if any other area of the application is working correctly.

The benefits of unit testing are:

  • Decouples your code
  • Write more modular classes
  • Functions are smaller and more focused
  • Your functions are more defensive
  • Quality of code becomes higher
  • You will find it easier to reuse code.

When writing unit tests you just need to test this one method of your application, if your method relies on another class/variable there should be a way you can inject this into the method. This is where dependency injection in your code comes in handy, it will allow you to inject objects into your classes to change the output of the class.

There are a few things you need to do to make a method unit testable, methods will need an input from a parameter or a class variable and it will need a return or set a class variable in the method. If the method hasn't got these things then the method can not be unit testable. If there isn't a return of the method then there is no way in knowing how the method performs.

Dependency Injection

Dependency injection is when your object has a dependency on another object. The simplest form to understand what dependency injection is to think of a setter method. A setter method will take one parameter and set a class variable from this parameter. This is using code injection to pass in a parameter to be used as the class variable value.

public function setValue( $val )
{
      $this->val = $val;
}

Without dependency injection this method will look like this.

public function setValue()
{
      $this->val = 10;
}

For unit testing you need to be aware of any classes that your class is dependent on. For example if you have a login class that will connect to a database.

class login
{
     private $db = false;
     
     public function __construct()
     {
          $this->db = new Database();
     }
            	
     public function loginUser( $user, $password )
     {
           $this->db->checkLogin( $user, $password );
     }
}

This login class has a dependency of the class Database in the constructor, which means that we can't unit test this correctly. If we want to unit test this then the database class has to be development and tested. If the database class is broken and we try to unit test the loginUser() method the test will always fail and we won't know that it's the database class which is broke or the loginUser() method that is broke.

If the database class is finished development, tested and data is in the database then we can use this for the loginUser() function. But now our tests are dependent on data being correct in the database. If we pass in a username and password it must be in the database for our test to pass. Our code could be correct but if the data isn't there then our unit tests will fail. This isn't correct use of unit tests and is more suited to be an integration test.

To fix this problem we can use dependency injection to pass in a database connector which will set the database class variable. There are 2 ways we can inject a variable into a class, it can either be in the constructor of the class or by using a setter method. I tend to use constructor for all required dependences and use the setter method if there is a default value for the class variable.

class login
{
     private $db = false;

     public function __construct( $db )
     {
          $this->db = $db;
     }
             	
     public function loginUser( $user, $password )
     {
          $this->db->checkLogin( $user, $password );
     }
}

Now this class isn't dependant on a certain database class we can pass in the database class by using the parameter on the login class constructor.

We can unit test this loginUser() method by first setting the $this->db class variable. We don't want to rely on a real database as the data can change so we can either create a test harness database class or you can mock the database class.

A test harness class will allow you to create your database class and hardcode any data that you need. In the example above we can create a method checkLogin(), in our test harness we can then hardcode a successful login username and password to make the loginUser() method pass. Or you can use a PHP mocking framework to mock a class/method/return value.

Both methods have their benefits but mocking is normally quicker to code, but there are times when you want to hardcode certain variables in a class.

Mocking Objects In TDD With PHP

Mocking objects in test driven development allows you create objects to act as a certain class, if your test depends on another method to return a value, you can mock this method and make it return any value you want.

In the example we used above you can mock the database class and choose what value we are expecting back from the checkLogin() method. When mocking a method you can choose what you want to return from this method, therefore we can write tests to see what will happen when checkLogin() returns TRUE and then we can write another test to see what happens when checkLogin() returns FALSE.

Mocking objects means that you can run your unit tests without depending on another class returning the values you are expecting, ao you can test just your code in this one method.

Here are some of the most popular PHP mocking frameworks:

Dependency Injection With Interfaces

If we are going to pass in a database connector in a constructor of the login class, then this database connector will always have to have a method of checkLogin(). This is why we should code our dependences by using interfaces to make sure that we are always passing in the correct type of class.

class login
{
     private $db = false;
     public function __construct( IDatabase $db )
     {
          $this->db = $db;
     }
}

class database implements IDatabase
{
     public function checkLogin( $username, $password )
     {
           // check the login credentials
     }
}

interface IDatabase
{
     public function checkLogin( $username, $password );
}

This will make sure that the class we pass into the constructor is a type of IDatabase, so if our database class doesn't implement IDatabase then the code will fail and therefore our unit tests will fail. This means whatever we pass into the constructor we know that this class will be able to run the methods it needs for the unit tests to run.

Advertise here 50% Off

Comment