Fork me on GitHub

Matchers

Note: Expectations can only be done inside it blocks.

Kahlan has a lot of matchers that can help you in your testing journey. All matchers can be chained.

it("can chain up a lots of matchers", function() {
   expect([1, 2, 3])->toBeA('array')->toBe([1, 2, 3])->toContain(1);
});

Classic matchers

toBe($expected)

it("passes if $actual === $expected", function() {
    expect(true)->toBe(true);
});

toEqual($expected)

it("passes if $actual == $expected", function() {
    expect(true)->toEqual(1);
});

toBeTruthy()

it("passes if $actual is truthy", function() {
    expect(1)->toBeTruthy();
});

toBeFalsy() / toBeEmpty()

it("passes if $actual is falsy", function() {
    expect(0)->toBeFalsy();
    expect(0)->toBeEmpty();
});

toBeNull()

it("passes if $actual is null", function() {
    expect(null)->toBeNull();
});

toBeA($expected)

it("passes if $actual is of a specific type", function() {
    expect('Hello World!')->toBeA('string');
    expect(false)->toBeA('boolean');
    expect(new stdClass())->toBeA('object');
});

Supported types:

  • string
  • integer
  • float (floating point numbers - also called double)
  • boolean
  • array
  • object
  • null
  • resource

toBeAnInstanceOf($expected)

it("passes if $actual is an instance of stdObject", function() {
    expect(new stdClass())->toBeAnInstanceOf('stdObject');
});

toHaveLength($expected)

it("passes if $actual has the correct length", function() {
    expect('Hello World!')->toHaveLength(12);
    expect(['a', 'b', 'c'])->toHaveLength(3);
});

toContain($expected)

it("passes if $actual contain $expected", function() {
    expect([1, 2, 3])->toContain(3);
});

toContainKey($expected)

it("passes if $actual contain $expected key(s)", function() {
    expect(['a' => 1, 'b' => 2, 'c' => 3])->toContainKey('a');
    expect(['a' => 1, 'b' => 2, 'c' => 3])->toContainKey('a', 'b');
    expect(['a' => 1, 'b' => 2, 'c' => 3])->toContainKey(['a', 'b']);
});

toBeCloseTo($expected, $precision)

it("passes if abs($actual - $expected)*2 < 0.01", function() {
    expect(1.23)->toBeCloseTo(1.225, 2);
    expect(1.23)->not->toBeCloseTo(1.2249999, 2);
});

toBeGreaterThan($expected)

it("passes if $actual > $expected", function() {
    expect(1)->toBeGreaterThan(0.999);
});

toBeLessThan($expected)

it("passes if $actual < $expected", function() {
    expect(0.999)->toBeLessThan(1);
});

toThrow($expected)

it("passes if $closure throws the $expected exception", function() {
    $closure = function() {
        // place the code that you expect to throw an exception in a closure, like so
        throw new RuntimeException('exception message');
    };

    expect($closure)->toThrow();
    expect($closure)->toThrow(new RuntimeException());
    expect($closure)->toThrow(new RuntimeException('exception message'));
});
it("calls the closure under the hood", function() {
    $count = 0;
    $closure = function() use (&$count) {
        count++;
    };
    expect($closure)->not->toThrow(); // Closure called here
    expect($count)->toBe(1);
});

toMatch($expected)

it("passes if $actual matches the $expected regexp", function() {
    expect('Hello World!')->toMatch('/^H(.*?)!$/');
});
it("passes if $actual matches the $expected closure logic", function() {
    expect('Hello World!')->toMatch(function($actual) {
        return $actual === 'Hello World!';
    });
});

toEcho($expected)

it("passes if $closure echoes the expected output", function() {
    $closure = function() {
        echo "Hello World!";
    };

    expect($closure)->toEcho("Hello World!");
});

toMatchEcho($expected)

it("passes if $closure echoes the expected regex output", function() {
    $closure = function() {
        echo "Hello World!";
    };

    expect($closure)->toMatchEcho('/^H(.*?)!$/');
});
it("passes if $actual matches the $expected closure logic", function() {
    expect(function() { echo 'Hello World!'; })->toMatchEcho(function($actual) {
        return $actual === 'Hello World!';
    });
});

Method invocation matchers

Note: You should always remember to use toReceive function before you call a method.

toReceive($expected)

it("expects $foo to receive message() with the correct param", function() {
    $foo = new Foo();

    expect($foo)->toReceive('message')->with('My Message');
    expect($foo->message('My Message'))->toBe($foo);
});
it("expects $foo to receive message() and bail out using a stub", function() {
    $foo = new Foo();

    allow($foo)->toReceive('message')->andReturn('something');
    expect($foo->message('My Message'))->toBe('something');
});

Note: When andReturn()/andRun() is not applied, toReceive() simply act as a "spy" and let the code execution flow to be unchanged. However when applied, the code execution will bail out with the stub value.

it("expects $foo to receive message() and bail out using a closure for stub", function() {
    $foo = new Foo();

    expect($foo)->toReceive('message')->andRun(function() {
        return 'something';
    });
    expect($foo->message('My Message'))->toBe('something');
});
it("expects Foo to receive ::message() with the correct param", function() {
    expect(Foo::class)->toReceive('::message')->with('My Message');
    Foo::message('My Message');
});
it("expects Foo to receive ::message() with the correct param only once", function() {
    expect(Foo::class)->toReceive('::message')->with('My Message')->once();
    Foo::message('My Message');
});
it("expects Foo to receive ::message() with the correct param a specified number of times", function() {
    expect(Foo::class)->toReceive('::message')->with('My Message')->times(2);
    $foo::message('My Message');
    $foo::message('My Message');
});
it("expects $foo to receive message() followed by foo()", function() {
    $foo = new Foo();
    expect($foo)->toReceive('message')->ordered;
    expect($foo)->toReceive('foo')->ordered;
    $foo->message();
    $foo->foo();
});
it("expects $foo to receive message() but not followed by foo()", function() {
    $foo = new Foo();
    expect($foo)->toReceive('message')->ordered;
    expect($foo)->not->toReceive('foo')->ordered;
    $foo->foo();
    $foo->message();
});

Note: You should pay attention that using such matchers will make your tests more "fragile" and can be identified as code smells even though not all code smells indicate real problems.

Function invocation matchers

Note: You should always remember to use toBeCalled function before you call a method.

toBeCalled()

it("expects `time()` to be called", function() {
    $foo = new Foo();
    expect('time')->toBeCalled();
    $foo->date();
});

Note: When andReturn()/andRun() is not applied, toBeCalled() simply act as a "spy" and let the code execution flow to be unchanged. However when applied, the code execution will bail out with the stub value.

it("expects `time()` to be called", function() {
    $foo = new Foo();
    allow('time')->toBeCalled()->andReturn(strtotime("now"));
    expect('time')->toBeCalled();
    $foo->date();
});
it("expects `time()` to be called", function() {
    $foo = new Foo();

    allow('time')->toBeCalled()->andRun(function() {
        return strtotime("now")
    });
    expect('time')->toBeCalled();

    $foo->date();
});
it("expects `time()` to be called with the correct param only once", function() {
    $foo = new Foo();
    expect('time')->toBeCalled()->with()->once();
    $foo->date();
});
it("expects `time()` to be called and followed by `rand()`", function() {
    $foo = new Foo();
    expect('time')->toBeCalled()->ordered;
    expect('rand')->toBeCalled()->ordered;
    $foo->date();
    $foo->random();
});
it("expects `time()` to be called and followed by `rand()`", function() {
    $foo = new Foo();
    expect('time')->toBeCalled()->ordered;
    expect('rand')->toBeCalled()->ordered;
    $foo->random();
    $foo->date();
});

Argument Matchers

To enable Argument Matching add the following use statement in the top of your tests:

use Kahlan\Arg;

With the Arg class you can use any existing matchers to test arguments.

it("expects args to match the argument matchers", function() {
    $foo = new Foo();
    expect($foo)->toReceive('message')->with(Arg::toBeA('boolean'))->ordered;
    expect($foo)->toReceive('message')->with(Arg::toBeA('string'))->ordered;
    $foo->message(true);
    $foo->message('Hello World!');
});
it("expects args to match the toContain argument matcher", function() {
    $foo = new Foo();
    expect($foo)->toReceive('message')->with(Arg::toContain('My Message'));
    $foo->message(['My Message', 'My Other Message']);
});
it("expects multiple args to match the argument matcher", function() {
    $foo = new Foo();
    expect($foo)->toReceive('message')->with(Arg::toBe('Hello World!'), Arg::toBeA('boolean'));
    $foo->message('Hello World!', true);
});

Custom matchers

With Kahlan you can easily create you own matchers. Long story short, a matcher is a simple class with a least a two static methods: match() and description().

Example of a toBeZero() matcher:

namespace my\namespace;

class ToBeZero
{
    public static function match($actual, $expected = null)
    {
        return $actual === 0;
    }

    public static function description()
    {
        return "be equal to 0.";
    }
}

Once created you only need to register it using the following syntax:

Kahlan\Matcher::register('toBeZero', 'my\namespace\ToBeZero');

Note: custom matcher should be reserved to frequently used matching. For other cases, just use the toMatch matcher using the matcher closure as parameter.

It's also possible to register a matcher to work only for a specific class name to keep the API consistent.

Example:

Kahlan\Matcher::register('toContain', 'my\namespace\ToContain', ' SplObjectStorage');