Running Superdesk Publisher Tests

The Superdesk Publisher project uses a third-party service which automatically runs tests for any submitted patch. If the new code breaks any test, the pull request will show an error message with a link to the full error details.

In any case, it’s a good practice to run tests locally before submitting a patch for inclusion, to check that you have not broken anything.

Before Running the Tests

To run the Superdesk Publisher test suite, install the external dependencies used during the tests, such as Doctrine, Twig and Monolog. To do so, install Composer and execute the following:

1
composer install

Note

For unit tests we use PHPSpec, for functional tests PHPUnit and Behat for integration.

Running the PHPUnit Tests

Then, run the test suite from the Superdesk Publisher root directory with the following command:

1
bin/phpunit -c app/

The output should display OK. If not, read the reported errors to figure out what’s going on and if the tests are broken because of the new code.

Tip

The entire Superdesk Publisher suite can take up to several minutes to complete. If you want to test a single component/bundle, type its path after the phpunit command, e.g.:

1
bin/phpunit src/SWP/Bundle/MultiTenancyBundle/

Tip

On Windows, install the ConEmu, ANSICON or Mintty free applications to see coloured test results.

Running the PHPSpec specs

Note

This section is based on Sylius documentation.

PHPSpec is a PHP toolset to drive emergent design by specification. It is not really a testing tool, but a design instrument, which helps structuring the objects and how they work together.

The Superdesk Publisher approach is to always describe the behaviour of the next object you are about to implement.

As an example, we’ll write a service, which sets the current tenant in the context. To initialize a new spec, use the desc command.

We just need to tell PHPSpec we will be working on the TenantContext class.

1
2
bin/phpspec desc "SWP\Component\MultiTenancy\Context\TenantContext"
Specification for TenantContext created in spec.

What have we just done? PHPSpec has created the spec for us. You can navigate to the spec folder and see the spec there:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
<?php

namespace spec\SWP\Component\MultiTenancy\Context;

use PhpSpec\ObjectBehavior;
use Prophecy\Argument;

class TenantContextSpec extends ObjectBehavior
{
    function it_is_initializable()
    {
        $this->shouldHaveType('SWP\Component\MultiTenancy\Context\TenantContext');
    }
}

The object behaviour is made of examples. Examples are encased in public methods, started with it_ or its_.

PHPSpec searches for such methods in your specification to run. Why underscores for example names? just_because_its_much_easier_to_read than someLongCamelCasingLikeThat.

Now, let’s write the first example, which will set the current tenant:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php

namespace spec\SWP\Component\MultiTenancy\Context;

use PhpSpec\ObjectBehavior;
use SWP\Component\MultiTenancy\Model\TenantInterface;

class TenantContextSpec extends ObjectBehavior
{
    function it_is_initializable()
    {
        $this->shouldHaveType('SWP\Component\MultiTenancy\Context\TenantContext');
    }

    function it_should_set_tenant(TenantInterface $tenant)
    {
        $tenant->getId()->willReturn(1);
        $tenant->getSubdomain()->willReturn('example1');
        $tenant->getName()->willReturn('example1');

        $this->setTenant($tenant)->shouldBeNull();
    }
}

The example looks clear and simple, the TenantContext service should obtain the tenant id, name, subdomain and call the method to set the tenant.

Try running the example by using the following command:

1
2
3
4
5
6
7
8
bin/phpspec run

> spec\SWP\Component\MultiTenancy\Context\TenantContext

  ✘ it should set tenant
      Class TenantContext does not exists.

         Do you want me to create it for you? [Y/n]

Once the class is created and you run the command again, PHPSpec will ask if it should create the method as well. Start implementing the initial version of the TenantContext.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php

namespace SWP\Component\MultiTenancy\Context;

use SWP\Component\MultiTenancy\Model\TenantInterface;

/**
 * Class TenantContext.
 */
class TenantContext implements TenantContextInterface
{
    /**
     * @var TenantInterface
     */
    protected $tenant;

    /**
     * {@inheritdoc}
     */
    public function setTenant(TenantInterface $tenant)
    {
        $this->tenant = $tenant;
    }
}

Done! If you run PHPSpec again, you should see the following output:

1
2
3
4
5
6
7
8
bin/phpspec run

> spec\SWP\Component\MultiTenancy\Context\TenantContext

  ✔ it should set tenant

1 examples (1 passed)
123ms

This example is greatly simplified, in order to illustrate how we work. More examples might cover errors, API exceptions and other edge-cases.

A few tips & rules to follow when working with PHPSpec & Superdesk Publisher:

  • RED is good, add or fix the code to make it green;
  • RED-GREEN-REFACTOR is our rule;
  • All specs must pass;
  • When writing examples, describe the behaviour of the object in the present tense;
  • Omit the public keyword;
  • Use underscores (_) in the examples;
  • Use type hinting to mock and stub classes;
  • If your specification is getting too complex, the design is wrong. Try decoupling a bit more;
  • If you cannot describe something easily, probably you should not be doing it that way;
  • shouldBeCalled or willReturn, never together, except for builders;
  • Use constants in assumptions but strings in expected results;