Quick Start

Create your first spec in under five minutes. This guide walks through the red-green cycle that makes phpspec tick.

1. Install phpspec

$ composer require --dev phpspec/phpspec

2. Generate a spec

Use the describe command to scaffold a spec file:

$ bin/phpspec describe App/Calculator

This creates spec/App/Calculator.spec.php:

<?php

use App\Calculator;

describe(Calculator::class, function() {
    let("calculator", fn() => new Calculator());

    it("instantiates", fn() =>
        expect($this->calculator)->toBeAnInstanceOf(Calculator::class)
    );
});

3. Run it — Red

$ bin/phpspec run

The class App\Calculator doesn’t exist yet. phpspec will ask:

phpspec run
Looks like you are trying to spec App\Calculator,
a class that doesn't exist yet.

Would you like me to generate that class for you? [y/n] y

Class App\Calculator generated in src/App/Calculator.php

4. Add behaviour

Open spec/App/Calculator.spec.php and add an example:

describe(Calculator::class, function () {
    let('calculator', fn() => new Calculator());

    it('adds two numbers', function () {
        expect($this->calculator->add(2, 3))->toBe(5);
    });
});

Run again — the add() method doesn’t exist. phpspec offers to generate the stub. Say y, then implement it:

namespace App;

class Calculator
{
    public function add(int $a, int $b): int
    {
        return $a + $b;
    }
}

5. Run again — Green

phpspec run
Once you spec, you never go back!

Calculator
  Calculator
     adds two numbers

1 spec
1 example (1 pass)
Finished in 0.0031 seconds

Using expect()

Every assertion uses the expect() function followed by a matcher:

// Identity (===)
expect($value)->toBe(5);

// Negation
expect($value)->not()->toBe(3);

// String contains
expect($greeting)->toContain('hello');

// Exceptions
expect(fn() => $calc->divide(1, 0))
    ->toThrow(\InvalidArgumentException::class);

The describe / context / it pattern

Nest context blocks to organise examples by scenario:

describe(Calculator::class, function () {
    context('when adding', function () {
        it('adds positive numbers', function () {
            expect((new Calculator())->add(2, 3))->toBe(5);
        });
    });

    context('when dividing', function () {
        it('throws on division by zero', function () {
            expect(fn() => (new Calculator())->divide(1, 0))
                ->toThrow(\InvalidArgumentException::class);
        });
    });
});

Sharing state with let

Use let() to define values that are re-evaluated fresh for each example:

describe(Calculator::class, function () {
    let('calculator', fn() => new Calculator());

    it('adds', function () {
        expect($this->calculator->add(1, 1))->toBe(2);
    });

    it('subtracts', function () {
        expect($this->calculator->subtract(5, 3))->toBe(2);
    });
});

Access let values via $this->name. Type-hinted parameters auto-inject mocks — see Mocking.