2

I have a sample class which performs mysql operations as shown below.

<?php
class ProjectHandler{
    public function getProjectInformation($projectId){
        $prMysql = new prMysql;
        /* Make connection to database */
        $connection = $prMysql->open_store_database();
        $sql = sprintf("SELECT product_id, projectName FROM test_projects
                WHERE projectId = %d", $platform_id);
        $test_result = mysql_query($sql, $connection) or die();
        $num_rows = mysql_num_rows($test_result);
        if ($num_rows > 0) {
            $test_row = mysql_fetch_assoc($test_result);
        }
        return $test_row;
    }
}
?>

Where prMysql is a wrapper class written for mysql operations. Is it possible to mock the mysql calls in the scenario?

Dezza
  • 1,094
  • 4
  • 22
  • 25
Siva Ninala
  • 21
  • 1
  • 5

1 Answers1

5

Yes, you can do it

There are two approaches that you could take to this. Both have their drawbacks given the code sample you provided.

I'm going to assume that mysql_query() is a typo, and you meant mysqli_query(). See How can I prevent SQL injection in PHP? for more info on why you should not be using the mysql_* functions and what you can do instead.

Use dependency injection

Given your statement

Where prMysql is a wrapper class written for mysql operations

I am, for the purposes of this part of the answer, going to assume that the calls such as mysql_query($sql, $connection) are actually written as $prMysql->query($sql), thus wrapping the actual call. I'm also going to assume that the num_results() part is done in that method too. If you opt for this refactoring solution, the prMysql class will need to be used for querying, as well as just the connection.

The idea behind dependency injection is that you don't instantiate the dependencies (prMysql in your case) inside the class that is using it, but outside of it. This comes in useful when it comes to unit testing because you can give a fake, or "mock" object to the class that is under test. This mock object can be constructed such that it always returns a known value to a known input.

The downside in your case of this approach is that you will need to refactor the ProjectHandler class to have this passed as a dependency. Something like the following should suffice, but you might need to play around with where you have the open connection call:

class ProjectHandler
{
    /** @var prMysql */
    private $database;

    /**
     * @param prMysql $database
     */
    public function __construct(prMysql $database)
    {
        $this->database = $database;
    }

    /**
     * @param mixed $projectId
     * @return array
     */
    public function getProjectInformation($projectId)
    {
        $prMysql = $this->database;
        $sql = sprintf("SELECT product_id, projectName FROM test_projects
            WHERE projectId = %d", $platform_id);
        $test_row = $pyMysql->query($sql);
        return $test_row;
    }
}

What this means is that you can easily give a mock prMysql object while testing without having to change any of the code of the system under test. In your test method you could then have something like the following:

public function testGetProjectInformation()
{
    // Here, we create a mock prMysql object so we don't use the original
    $prMysql = $this->getMockBuilder(prMysql::class)
        ->disableOriginalConstructor()
        ->getMock();

    /* Here, we say that we expect a call to mysql_query with a given query,
     * and when we do, return a certain result.
     * You will also need to mock other methods as required */
    $expectedQuery = "SELECT product_id, projectName FROM test_projects
            WHERE projectId = 1";
    $returnValue = [['product_id' => 1, 'projectName' => 'test Name']];
    $prMysql->expects($this->once())
        ->method('query')
        ->with($this->equalTo($expectedQuery))
        ->willReturn($returnValue);

    // Here we call the method and do some checks on it
    $object = new ProjectHandler($prMysql);
    $result = $object->getProjectInformation(1);
    $this->assertSame($returnValue, $result);
}

Now, please remember that this is just a rough sketch of what you need to do. You will need to fill in the details yourself. There's a fair amount of refactoring to do, but it will be worth it in the end.

Set up a mock database

The other alternative is to set up a database just for testing, and connect directly to this.

There is a whole chapter in the phpunit manual about this, but what it boils down to is, for each test:

  • Setting the database to a known state
  • Running the test
  • Asserting that certain tables are in a given state
  • Tearing down the connection

In your case this has the advantage that you will have to change little, if any, of your code. This has the disadvantage that your tests will be very slow, and you have lost the abstraction that dependency injection etc. would give.

miken32
  • 42,008
  • 16
  • 111
  • 154
Dezza
  • 1,094
  • 4
  • 22
  • 25
  • Thanks for a brief explanation. one more quick question, is not possible write unittests with out altering the source code? – Siva Ninala Dec 04 '16 at 17:47
  • @Siva Ninala Yes, see the second part of my answer, titled "Set up a mock database". This will cause your tests to be slow, and you won't have any abstraction though. – Dezza Dec 04 '16 at 20:13