How to create a Model object in Yii PHPUnit test without a fixture?

778 views Asked by At

I am writing a PHPUnit test for my Yii application. I read here:

Tip: Having too many fixture files could increase the test time dramatically. For this reason, you should only provide fixture files for those tables whose content may change during the test. Tables that serve as look-ups do not change and thus do not need fixture files.

I indeed have a large fixture (180 records, which takes >20 seconds to load), which is only used as a look-up. However, I do need to transform it easily from an associative array into a Model object, like you can usually do with the fixture syntax below. The tip suggests that there is also a way to create a Model object without the use of a fixture, but does not mention how this is done. Can anyone help out?

Creation of Model object with a fixture:

// tests/fixtures/Order.php
return array(
    'row_id' => array(
        'id' => 1,
        'name' => 'hello',
    )
)

// tests/unit/AbcTest.php
public $fixtures = array(
    'orders' => 'Order',
)

public test_abc()
{
    $order = $this->orders('row_id');
    ....
}
2

There are 2 answers

1
cn007b On BEST ANSWER

Another option:
when you create db migration you should apply it on production db and on test db moreover you should populate test tables with test data.

Benefits of this approach:

  1. You will run populate sql just once (not like fixture - each time invokes test).
  2. Your test will executes fast, because db will be prepared.
  3. When you commit new feature that need new test with new data in db - you create db migration, and it will be executed just once, in explicit way.

For example:

<?php

class m150608_110143_init extends CDbMigration
{
    public function safeUp()
    {
        $sql1 = "
            CREATE TABLE brand (
                id INT AUTO_INCREMENT,
                name VARCHAR(100) NOT NULL DEFAULT '',
                country VARCHAR(50) NOT NULL DEFAULT '',
                PRIMARY KEY (id)
            );
        ";
        $sql2 = "
            INSERT INTO brand VALUES
                (null, 'aston martin', 'UK'),
                (null, 'audi', 'Germany'),
                (null, 'bmw', 'Germany'),
                (null, 'citroen', 'France'),
                (null, 'peugeot', 'France'),
                (null, 'porsche', 'Germany'),
                (null, 'toyota', 'Japan'),
                (null, 'ferrari', 'Italy')
            ;
        ";
        // Production db.
        $this->setDbConnection(Yii::app()->db);
        $this->execute($sql1);
        // Test db.
        $this->setDbConnection(Yii::app()->dbUnitTest);
        $this->execute($sql1);
        // Populate test db with fixtures.
        $this->execute($sql2);
        return true;
    }

    public function down()
    {
        $sql = 'DROP TABLE brand;';
        // Test db.
        $this->setDbConnection(Yii::app()->dbUnitTest);
        $this->execute($sql);
        // Production db.
        $this->setDbConnection(Yii::app()->db);
        $this->execute($sql);
        return true;
    }
}

And in test you don't have to think about fixtures.

2
cn007b On

Yes, it's possible to reach what you wish.
For example: i have model brand that have own fixture:

<?php
// protected/tests/fixtures/brand.php

return [
    1 => [
        'name' => 'Lexus',
        'country' => 'JPN',
    ],
    2 => [
        'name' => 'Acura',
        'country' => 'JPNE',
    ],
];

and i have next code:

    $brand = new Brand;
    $allBrands = $brand->findAll();

This code will return array with 2 CActiveRecord objects. And everything what we need - it's just build the same array with 2 CActiveRecord objects:

public function testGetAllAvailableBrands()
{
    // Get brands from fixture.
    $brand = new Brand;
    $allBrandsFromFixture = $brand->findAll();
    // Generate brands.
    $lexus = new Brand;
    $lexus->scenario = 'update';
    $lexus->name = 'Lexus';
    $lexus->country = 'JPN';
    $lexus->id = 1;
    $lexus->setPrimaryKey(1);
    $lexus->setIsNewRecord(false);
    $allBrandsGeneratedAtRuntime[] = $lexus;
    $acura = new Brand;
    $acura->scenario = 'update';
    $acura->name = 'Acura';
    $acura->country = 'JPNE';
    $acura->id = 2;
    $acura->setPrimaryKey(2);
    $acura->setIsNewRecord(false);
    $allBrandsGeneratedAtRuntime[] = $acura;
    // Brands from fixture should be equals to generated brands.
    $this->assertEquals($allBrandsFromFixture, $allBrandsGeneratedAtRuntime);
}

This test will be green because our brands exactly the same. You cat try something like this.

But i think that native yii fixtures looks much better, and to avoid increase the test time you should use .init.php files...