如何在单元测试中捕获无效的查询错误

I introduced unit-testing a few month ago in our development process.

I just faced this problem : I wrote a test case for a simple function and relies on a database query. I used a mock object instead of the database adapter object. The tests passed, so far so good.

But it was a false positive. The mock object returned the expected resultset form the database (it must, since it is a mock object), but the real query did not return a valid resultset because of a typo.

One way I could solve this problem is to throw an exception if the query does not yield a recordset.

$db->query($query);
$resultset = $db->fetchAll();
if(!is_array($resultset)) throw new UnexpectedValueException("Query does not yield a result set");

This approach just makes the whole script fail (if the exception is not catched), and the test will fail for sure.

My question is : Is this the correct approach, or would you recommend something else ?

IMO your approach is wrong. Why?

  1. You do not test that models correctly working with database (e.g. insert, update or delete records, selecting records).

  2. You can't check the queries are OK.

How we do this?

We use migrations system (https://github.com/ruckus/ruckusing-migrations) that allows us to change databases easily. Our system has two databases - for production and for tests.

When we create a new change (e.g. with some alter column) we run migration which apply changes to these databases.

In test we use transactions that are rolled back after each test. You can look at this - https://github.com/letsdrink/ouzo/blob/master/src/Ouzo/Core/Tests/DbTransactionalTestCase.php (this is our framework) as you can see there are override method setUp and tearDown which start and roll-back transactions after each test. You should write an integration test.

How does this work?

You can look for concrete example here.

public function shouldPersistModel()
{
    //given
    $product = new Product();
    $product->name = 'Sport';

    //when
    $id = $product->insert();

    //then
    $this->assertNotNull($id);
    $actual = Product::findById($id);
    $this->assertEquals('Sport', $actual->name);
}

Work-flow:

  1. Run method setUp which is starting transaction.
  2. Create object Product and inserting to the database.
  3. Fetch concrete object.
  4. Assertion.
  5. Run method tearDown which roll-back database to previous state.

And here is link to the docs.