Category Archives: PhpUnit Tests

setUp and tearDown the Repository Test

The Repository Test needs some specific settings (like the configuration and some test fixtures). They are setup by the – wow – setUp() method (nomen est omen) and all settings are taken back by the tearDown() method. If you overwrite these methods, don’t forget to call the parent’s methods as well in case that the parent class wants to setup and restore some test stuff as well.

I start with the phpunit test framework to setup test data in the database. Afterwards I set the extension’s configuration like described before. In the end I retrieve an instance of the repository class that is to be tested. In the tearDown() method I just do all of this in the opposite way:


protected function setUp() {
    parent::setUp();
    $this->testingFramework = new Tx_Phpunit_Framework('tx_csevents');
    $this->setupConfiguration();
    $this->appointmentRepository = $this->objectManager->get(Tx_Csevents_Domain_Repository_AppointmentRepository);
}

protected function tearDown() {
    unset($this->appointmentRepository);
    $this->restoreConfiguration();
    $this->testingFramework->cleanUp();
    unset($this->testingFramework);
    parent::tearDown();
}

Now everything is ready to create test data in the database, run tests on the repository and check, if all configuration mappings work properly.

The Repository Unit Test

‘Til now we were in a very straigt-forward situation: Setup your environment, read a book to learn the first steps, define your strategy, start programming. Now it’s time to meet the first challenges slowing you down and proving you, if you take your decisions really seriously.

The challenge

I want to go on with the Test First Approach for the Entity Repository. I have to consider that the environment is not a from-scratch-standard-framework-setup: The database tables and columns are not named like the framework would expect it to be. Thus I need a configuration to map the database names and columns to the domain model entity classes and properties.

The configuration itself is not a problem. I just define it in [extkey]/ext_typoscript_setup.txt:

config.tx_csevents {
  persistence {
    classes {
      Tx_Csevents_Domain_Model_Appointment {
        mapping {
          tableName = tx_csevents_appointments
          recordType = Tx_Csevents_Domain_Model_Appointment
          columns {
            listas.mapOnProperty = listAs
            stime.mapOnProperty = eventStartTime
            letin.mapOnProperty = eventLetInTime
            etime.mapOnProperty = eventEndTime
            places.mapOnProperty = numOfSeatsAvailable
            soldout.mapOnProperty = isSoldOut
          }
        }
      }
    }
  }
}

“tabelName” and “recordType” map the database table and the entity object. The values defined in “columns” define the mapping between the database columns and the entity object’s properties.

This mapping will work in the running extension, but if we just run a unit tests, this configuration is ignored at all. Why? It’s because the unit test is not run by the extension itself but by the extbase framework. Thus it uses the extbase configuration and not the [extkey] (=”csevents”) configuration in this case. But to run the unit tests successfully, I need to use my own extension’s configuration.

The Solution

A lot of ideas failed, but finally a semi-official way was successful. Let’s have a look at the solution:

  1. Get the configuration object
  2. Safe the current (extbase) configuration in a local property to restore it at the end
  3. Read the whole TypoScript configuration and extract the own (csevents) configuration
  4. Overwrite the current (extbase) configuration by the extracted (csevents) configuration
  5. Do the tests
  6. Restore the (extbase) configuration to ensure that all following processes use the original configuration

Steps 1-4 are done in the test’s setUp() method, step 5 is just the set of tests in the test classe and step 6 is done by the test’s tearDown() method. To make things a little easier, I just put steps 1-4 and 6 in separate methods instead of just including them directly into the setUp() and tearDown() methods. Let’s have a look:


protected function setupConfiguration() {
    $configurationManager = $this->objectManager->get(Tx_Extbase_Configuration_ConfigurationManager);
    $this->extbaseConfiguration = $configurationManager->getConfiguration(Tx_Extbase_Configuration_ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK);
    $fullTypoScript = $configurationManager->getConfiguration(Tx_Extbase_Configuration_ConfigurationManagerInterface::CONFIGURATION_TYPE_FULL_TYPOSCRIPT);
    $myConfig = $fullTypoScript['config.']['tx_csevents.'];
    $configurationManager->setConfiguration($myConfig);
}

protected function restoreConfiguration() {
    $configurationManager = $this->objectManager->get(Tx_Extbase_Configuration_ConfigurationManager);
    $configurationManager->setConfiguration($this->extbaseConfiguration);
}

Line 2: get the configuration manager
Line 3: get the current framework configuration (i.e. the extbase configuration) and safe it internally
Line 4: get the full TS configuration
Line 5: extract my own extension’s configuration
Line 6: overwrite the current framework (=extbase) configuration by the extracted one

This is semi-official, because the method setConfiguration() is marked as an extbase-internal method and therefor shall not be used from the outside, but there was no other effective and efficient solution.

Line 10: get the configuration manager (again, but this is a Singleton, thus you will get the same instance again)
Line 11: overwrite the current (own) configuration by the safed (extbase) configuration

If this would be the only test, restoring the configuration wouldn’t be necessary, but there is a simple rule about testing (it’s the same like on the toilet): leave it in the same status like you found it entering. Thus we restore everything we changed before. Now it’s ensured that all following tests and activities will use the original configuration again. This is pretty and clear.

Dead ends

I tried a lot to get this running. Some notes about things that didn’t work or that may work, but I decided that it’s much too inefficient / complex to do:

  1. Tell the Configuration Manager to use your own configuration
    As noted above, the Configuration Manager reads the extbase’s configuration, because the unit test is running under extbase’s environment. Thus the extension key “extbase” is used to find the configuration. You might try to change this by the Configuration Manager’s method “getConfiguration(CONFIGURATION_TYPE_FRAMEWORK, ‘yourExtensionKey’, ‘pi1’)”, but this will only work for the values you retrieve from this call directly. It won’t work for the repository backend, which still gets it’s information from getConfiguration(…) using “extbase” as extension key. Dead end #1 reached.
  2. Try to setup a (simulated) frontend
    You might think “If the extension works like real life (i.e. in the frontend), it will also use it’s own configuration. So let’s simulate the frontend and the TS configuration will be alright.” 1. It simply doesn’t work. The test is still run in extbase’s environment. 2. It’s bad style to try to simulate a frontend while testing in the backend just to get the right configuration. It’s about testing the domain model and the repository. There is no frontend involved in the test at all. To use a frontend just for configuration purposes is the wrong way. Dead end #2 reached.
  3. Configure extbase in the same way like you configure your own extension
    Idea #3: Copy the TypoScript configuration via “<” or “<=” notation into the extbase configuration. If you do so, extbase uses simply it’s own configuration and this configuration is the same like that one used by your extension in real life. Hmmm. You must do copy&paste and/or you must use a lot of “<” references in TypoScript to include (merge) your extension’s configuration into the existing extbase configuration. Sooner or later you will forget parts of your configuration and a) struggle to find the bug or (worse) b) successfully test with a configuration that does not represent the actual truth. Awful end #3 reached. (Of course you can do it like this, thus it’s not a “dead” end actually, but it’s a very bad style, because of the potential inconsistency and changing another extension’s configuration.)
  4. Maybe that there are much more dead ends, but these were the once, I tried or (at least) though of (actually I never tried to do the 3rd one, because of it’s weaknesses).

Making it “all green”

Of course the test will fail, because the class and all it’s functionality is missing. Thus it’s now time to setup the Classes-path and the according domain model entity classes. They are just simple. Getters, setters, some fields, done.

So here we are:The Entity Class 'Price'


<?php
// $Id: Price.php 24 2014-01-26 21:18:24Z *** $
//namespace TYPO3\CMS\Csevents\Domain\Model

/**
 * This class provides the entity object for the price settings.
 *
 * @author ***
 */
class Tx_Csevents_Domain_Model_Price extends Tx_Extbase_DomainObject_AbstractEntity {
    /**
     * @var string the name of this price setting
     */
    protected $name;

    /**
     * @var string the normal price
     */
    protected $normalPrice;

    /**
     * @var string the reduced price
     */
    protected $reducedPrice;

    /**
     * Set the name of the price setting.
     * @param string $name Name of the price setting
     */
    public function setName($name) {
        $this->name = $name;
    }

    /**
     * Get the name of the price setting.
     * @return string Name of the price setting
     */
    public function getName() {
        return $this->name;
    }

    /**
     * Set the normal price.
     * @param string $normalPrice Normal price
     */
    public function setNormalPrice($normalPrice) {
        $this->normalPrice = $normalPrice;
    }

    /**
     * Get the normal price.
     * @return string Normal price
     */
    public function getNormalPrice() {
        return $this->normalPrice;
    }

    /**
     * Set the reduced price.
     * @param string $reducedPrice Reduced price
     */
    public function setReducedPrice($reducedPrice) {
        $this->reducedPrice = $reducedPrice;
    }

    /**
     * Get the reduced price.
     * @return string Reduced price
     */
    public function getReducedPrice() {
        return $this->reducedPrice;
    }
}
?>

This will work and – wow – the test is “all green”.

I have to admit, the test and the entity class are not very complex and it is a little boring to implement them. But this is the actual fundament of all your development and the whole application. It’s a very good feeling in the later process, if you know by each test run that everything – even the easiest entity objects – works like expected.

And it’s even getting better. Just think of the situation that you want to add or change a field of the domain model (e.g. change the price to be a numeric field only) later on. You will be glad to have a complete regression test and can use the failing tests to guide you through the refactoring. JUST DO IT!

The first test: PriceTest.php

This is the unit test for the business domain entity “Price”. The entity will store a name for the price category and two values: normal and reduced price. Thus we will simply test if we can set and get all these parameters to/from the entity object.


<?php
// $Id: PriceTest.php 29 2014-02-01 23:02:17Z *** $
//namespace TYPO3\CMS\Csevents\ @todo add namespace

/**
 * This class provides the unit tests for
 * \TYPO3\CMS\Csevents\Domain\Model\Price
 *
 * @author ***
 */
class Tx_Csevents_Tests_Unit_Domain_Model_PriceTest extends Tx_Extbase_Tests_Unit_BaseTestCase {
    /**
     * @var Tx_Csevents_Domain_Model_Price
     */
    protected $price;

    protected function setUp() {
        $this->price = $this->objectManager->get(Tx_Csevents_Domain_Model_Price);
    }

    protected function tearDown() {
        unset($this->price);
    }

    /**
     * @test
     */
    public function canSetAndGetName() {
        $name = 'TestPriceName';
        $this->price->setName($name);
        $this->assertEquals($name, $this->price->getName());
    }

    /**
     * @test
     */
    public function canSetAndGetNormalPrice() {
        $normalPrice = '20.50$';
        $this->price->setNormalPrice($normalPrice);
        $this->assertEquals($normalPrice, $this->price->getNormalPrice());
    }

    /**
     * @test
     */
    public function canSetAndGetReducedPrice() {
        $reducedPrice = '12.50€ only';
        $this->price->setReducedPrice($reducedPrice);
        $this->assertEquals($reducedPrice, $this->price->getReducedPrice());
    }
}
?>

Note: I already added the namespace definition (line 3) as a comment to use it later on with Typo3 6.2.

You can use two different strategies here:

  1. Create a new entity object for each test via objectManager->get(…). Advantage: You always test on a completely fresh and virgin instance, there are no side effects. You don’t have to store the entity object within the test class, it’s just local to the specific test. Disadvantage: You create a lot of memory garbage and you won’t find unwanted side effects. You write/copy a lot of code many times to create and destroy the test objects.
  2. Create the entity just once prior to all test by setUp() and destroy it by using tearDown(). Advantage: You use only one instance and you have a chance to find side effects. The actual test code is not polluted with administrative stuff like creating and destroying test objects. Disadvantage: You have to store the entity object in the test class. You have to implement (resp. overwrite) two additional methods.

As you can see here, I used the second approach. Decide by your own, what’s the best solution for you.

Finger Exercise #2 – Unit Tests of the Domain Model

Let’s start implementing the first unit tests. Of course I do this in the first place, because I want to start with the tests first and do the actual implementation of the domain model’s functionality later on – driven by these tests.

But… This is a refactoring project and it looks like I am setting up a domain model from scratch. Is this refactoring?! Well, yes and no. The older (quick and dirty) implementation didn’t use any domain model classes at all. Everything was included in just one pi1-PHP file and the domain model was just a set of arrays containing data of database tables. Thus there is no specific part of the original software we can use as a reference. The only thing we can use as a reference is the database and it’s structure. If we find a domain model that meets the database model and if it reads and writes any data correctly from/to the database, the refactoring is successful. To achieve this, two steps are necessary. First implement a simple getter-setter-entity-classes domain model. Secondly add the corresponding repository to do the database communication.

Nevertheless the Test First Development forces us to do the tests for the domain model without the repository first. When these tests and the domain model implementation are set up and tested successfully, we will start to do the same for the repository as well. Unfortunately we have to wait for the refactoring result until this is done. But on the other side of the medal we can be sure that the domain model is working even before we start implementing the repository.

Thinking different: Cleaning up the domain model

You already know about the domain model. If you did – like I did – your development some years ago without thinking of the domain model in detail, you may find out now that your old database model does not fit to the actual domain model of your current destination. In case of my extension csevents I found out that the domain model e.g. contains a set of Appointments represented by various instances of the class “Appointment”. Some years ago when I set up the database, I called it “tx_csevents_apptointments”, where at least the last “s” is wrong, because it stores Appointment-objects and not Appointments-objects. Of course I don’t want to do the mistake twice, thus I will call it “Appointment” in my model as well as in my implementation and I have to map the “Appointment” model to the (wrongly named) database “tx_csevents_appointments”. This does not meet the extbase framework’s requirements, but it is a 1:1 refactoring for the old (DB-)model. We can think of and implement the database merge later on.

Why not doing it now, but knowing that we are doing something that has to be remodeled later on? – Just to keep it as small and simple as possible. First think of the PHP implementation and the extbase framework. Think of a database change including merge scripts for existing databases and documentation for users/admins later on. Divide et impera! It’s complex enough to do the PHP, testing and extbase topics for the beginning.

Ok, now we have to setup four test classes for the four domain model classes “Appointment”, “Description”, “Price” and “Category”. They are located at [extkey]/Tests/Unit/Domain/Model. The file “AppointmentTest.php” contains the class “Tx_Csevents_Tests_Unit_Domain_Model_AppointmentTest”. This will change in Typo3 6.2 when namespaces are introduced. For now and here with Typo3 4.5 namespaces are not available, thus we need to fall back to the Very_Long_Classname_Containing_The_Full_Path_Of_The_Namespace. (And accordingly the same for all four domain model objects.)

The Test FilesIn the next step let’s have a look at the first unit test implementation.