Working with Propel's Test Suite

    Propel uses PHPUnit to test the build and runtime frameworks.

    You can find the unit test classes and support files in the tests/ directory.

    Setup requirements

    Setup the environment

    You need to install the following PHP modules:

    • php5-mysql
    • php5-sqlite
    • php5-pgsql
    • php5-iconv

    Increase the memory_limit in your php.ini to something very high:

    memoy_limit = 512M
    

    Set a default timezone in your php.ini:

    date.timezone = Europe/Berlin
    

    InfoAll following commands need to be executed in the propel root directory.

    Install Dev Dependencies

    In order to run the tests, you must install the development dependencies. Propel uses Composer to manage its dependencies. To install it, you are done with:

    $ wget http://getcomposer.org/composer.phar
    

    Or if you don't have wget on your machine:

    $ curl -s http://getcomposer.org/installer | php
    

    Then run Composer to install the necessary stuff:

    $ php composer.phar install
    

    Setup Database

    We provide three scripts that setup your database.

    Those scripts drop basically all required databases/schemas (test, second_hand_books, contest, bookstore_schemas, migration) and re-create it, so the test suite has an empty database.

    tests/bin
    ├── setup.mysql.sh
    ├── setup.pgsql.sh
    └── setup.sqlite.sh
    

    Call one of those scripts in your console:

    ./tests/bin/setup.mysql.sh
    

    If your database is configured using a password or a different user than the default, you can configure it via environment variables.

    Example:

    DB_HOSTNAME=192.168.0.3 DB_USER=nonroot DB_PW=topsecret ./tests/bin/setup.mysql.sh
    DB_HOSTNAME=192.168.0.3 DB_USER=postgres DB_PW=secret ./tests/bin/setup.pgsql.sh
    ./tests/bin/setup.sqlite.sh #does not have any configuration ability
    

    Info./tests/bin/setup.sqlite.sh just removes the file at tests/test.sq3 to trigger a re-creating by our test suite.

    Writing Tests

    How the Tests Work

    Every method in the test classes that begins with 'test' is run as a test case by PHPUnit. All tests are run in isolation; the setUp() method is called at the beginning of ''each'' test and the tearDown() method is called at the end.

    The BookstoreTestBase class specifies setUp() and tearDown() methods which populate and depopulate, respectively, the database. This means that every unit test is run with a cleanly populated database. To see the sample data that is populated, take a look at the BookstoreDataPopulator class. You can also add data to this class, if needed by your tests; however, proceed cautiously when changing existing data in there as there may be unit tests that depend on it. More typically, you can simply create the data you need from within your test method. It will be deleted by the tearDown() method, so no need to clean up after yourself.

    Write first test

    If you've made a change to a template or to Propel behavior, the right thing to do is write a unit test that ensures that it works properly -- and continues to work in the future.

    Writing a unit test often means adding a method to one of the existing test classes. For example, let's test a feature in the Propel templates that supports saving of objects when only default values have been specified. Just add a testSaveWithDefaultValues() method to the GeneratedObjectTest class, as follows:

    <?php
    # tests/Propel/Tests/MyFirstTest.php
    
    namespace Propel\Tests;
    
    use Propel\Tests\TestCaseFixturesDatabase;
    
    /**
     * @group database
     */
    class MyFirstTest extends TestCaseFixturesDatabase
    {
        /**
         * Test saving object when only default values are set.
         */
        public function testSaveWithDefaultValues() {
    
          // Relies on a default value of 'Penguin' specified in schema
          // for publisher.name col.
    
          $pub = new Publisher();
          $pub->setName('Penguin');
            // in the past this wouldn't have marked object as modified
            // since 'Penguin' is the value that's already set for that attribute
          $pub->save();
    
          // if getId() returns the new ID, then we know save() worked.
          $this->assertNotNull($pub->getId(), "Expect Publisher->save() to work with only default values.");
        }
    }
    

    Run the test again using the command line to check that it passes:

    $ ./vendor/bin/phpunit GeneratedObjectTest
    

    You can also write additional unit test classes to any of the directories in test/testsuite/ (or add new directories if needed). The ./vendor/bin/phpunit command will find these files automatically and run them.

    Using the correct TestCase class

    Propel provides several test cases you can extend to have some additional functionalities.

    Propel\Tests\TestCase

    This class gives your some basic methods to see against which database the current suite is running at.

    Automatically Build Fixtures

    Propel\Tests\TestCaseFixtures

    This test case class makes sure all fixtures in tests/Fixtures/ have been build and are up to date. You have to use this class if you need built model loaded connection configurations.

    We place in tests/Fixtures/fixtures_built the current used settings/credentials used in the last running test suite.

    Automatically Build Fixtures and Update Database

    Propel\Tests\TestCaseFixturesDatabase

    This test case class makes, in addition to the one above, sure that the database schema is up to date as well. You have to use this if you want to have, in addition to the one above, a communication with the database through your models.

    Using the correct test group

    In our continuous integration setup we have four different builds running. One of those builds does execute tests that do not communicate with the database. So we need at each test class a @group database if that test does only work with configured database.

    Builds

    The following scripts are used to start phpunit in our build server.

    tests/bin/
    ├── phpunit.agnostic.sh
    ├── phpunit.mysql.sh
    ├── phpunit.pgsql.sh
    ├── phpunit.sqlite.sh
    

    You can see there that we filter by those groups:

    $ cat tests/bin/phpunit.agnostic.sh
    #!/bin/sh
    ./vendor/bin/phpunit --exclude-group database;
    
    $ cat tests/bin/phpunit.pgsql.sh
    #!/bin/sh
    ./vendor/bin/phpunit --group database --exclude-group mysql;
    

    and so on.

    The actual Travis build server is configured like this:

    script:
        - ./tests/bin/phpunit.$DB.sh;
    

    To group our tests we have basically only two @group annotation values that PHPUnit is going to filter for us.

    Example:

    <?php
    # tests/Propel/Tests/Generator/Reverse/MysqlSchemaParserTest.php
    /**
     * Tests for Mysql database schema parser.
     *
     * @author William Durand
     *
     * @group database
     */
    class MysqlSchemaParserTest extends TestCaseFixturesDatabase
    {
    

    @group database means this test requires a configured database.

    @group mysql means this test is mysql only.

    Running Unit Tests

    You start the whole test suite with default configuration (mysql, root, non-password) by just calling ./vendor/bin/phpunit in the Propel directory.

    Start only one test case

    You can pass a file path to phpunit to execute only all tests within this file.

    ./vendor/bin/phpunit tests/Propel/Tests/Runtime/ActiveQuery/CriteriaTest.php
    

    Use custom configuration/credentials

    You can use again environment variables to tell propel's test suite which database credentials it should use to build the fixtures and thus all connections.

    Examples:

    DB=pgsql DB_HOSTNAME=192.168.0.2 DB_USER=postgres DB_PW=topsecret ./vendor/bin/phpunit
    DB=sqlite ./vendor/bin/phpunit
    DB=mysql DB_HOSTNAME=192.168.0.2 DB_USER=nonroot DB_PW=topsecret ./vendor/bin/phpunit
    

    The best way to execute the test suite is to use our phpunit scripts:

    ./tests/bin/phpunit.agnostic.sh # non database tests only
    DB_USER=postgres DB_PW=secret ./tests/bin/phpunit.pgsql.sh
    ./tests/bin/phpunit.mysql.sh
    ./tests/bin/phpunit.sqlite.sh
    

    These scripts already filter by the required database.

    Rebuild fixtures

    Since we're creating or updating fixtures based on your environment configuration you don't have to call something special anymore to build those fixtures by hand. But if you change some schemas you still need a way to tell the test suite it has to re-build the whole thing. To do so just remove the file tests/Fixtures/fixtures_built where we store our current used test suite configuration.

    rm tests/Fixtures/fixtures_built
    

    If you change your credentials or adapter it will be automatically detected and triggers a rebuild as well.

    Specific tests only

    Since we're using test grouping we have some tests that work only with a configured database and tests that work only with mysql.

    To filter by those groups you can pass --group to the phpunit command.

    Only Non-MySQL database specific tests:

    ./vendor/bin/phpunit --group database --exclude-group mysql;
    

    Only database specific tests. Won't work for SQLite and PostgreSQL since phpunit includes here mysql tests as well, which will naturally fail on a non-mysql database:

    ./vendor/bin/phpunit --group database;
    

    Only non-database specific tests:

    ./vendor/bin/phpunit --exclude-group database;
    

    See Using the correct test group at this page to get more information about this.

    More Tips

    phpunit --stop-on-failure
    

    Stops on first failure so you don't have to wait until the whole test suite is done.

    phpunit --group test
    

    Execute only a subset of test methods that have @group test annotation. Example:

    /**
     * This tests tests the whole world.
     *
     * @group test
    */
    public function testWholeWorld(){}
    

    This is very handy if you're trying to fix or write one particular set of test methods and need to execute it again and again.

    Fix Coding Style

    You can fix your coding style before creating your commit by using the Symfony2 php-cs-fixer script. You just need to install the script:

    $ wget http://cs.sensiolabs.org/get/php-cs-fixer.phar
    

    Then use it on the file you have edited:

    $ php php-cs-fixer.phar fix $(git ls-files -m)