Giter VIP home page Giter VIP logo

kahlan's Introduction

Kahlan


Build Status License

Latest Stable Version Total Downloads Code Coverage

Kahlan is a full-featured Unit & BDD test framework a la RSpec/JSpec which uses a describe-it syntax and moves testing in PHP one step forward.

Kahlan lets you stub or monkey patch your code directly like in Ruby or JavaScript without any required PECL-extensions.

Videos

IRC

chat.freenode.net (server) #kahlan (channel)

Documentation

See the full documentation here

Requirements

  • PHP 7.2+
  • Composer
  • phpdbg or Xdebug (only required for code coverage analysis)

Main Features

  • RSpec/JSpec syntax
  • Code Coverage metrics (xdebug or phpdbg required)
  • Handy stubbing system (mockery or prophecy are no longer needed)
  • Set stubs on your class methods directly (i.e allows dynamic mocking)
  • Ability to Monkey Patch your code (i.e. allows replacement of core functions/classes on the fly)
  • Check called methods on your classes/instances
  • Built-in Reporters (Terminal or HTML reporting through istanbul or lcov)
  • Built-in Exporters (Coveralls, Code Climate, Scrutinizer, Clover)
  • Extensible, customizable workflow

Syntax

<?php

describe("Example", function() {

    it("makes an expectation", function() {

         expect(true)->toBe(true);

    });

    it("expects methods to be called", function() {

        $user = new User();
        expect($user)->toReceive('save')->with(['validates' => false]);
        $user->save(['validates' => false]);

    });

    it("stubs a function", function() {

        allow('time')->toBeCalled()->andReturn(123);
        $user = new User();
        expect($user->save())->toBe(true)
        expect($user->created)->toBe(123);

    });

    it("stubs a class", function() {

        allow('PDO')->toReceive('prepare', 'fetchAll')->andReturn([['name' => 'bob']]);
        $user = new User();
        expect($user->all())->toBe([['name' => 'bob']]);

    });

});

Screenshots

Example of default reporting:

dot_reporter

Example of verbose reporting:

verbose_reporter

Example of code coverage on a specific scope:

code_coverage

Installation

via Composer

$ composer require --dev kahlan/kahlan

Note: Kahlan uses the Semantic Versioning and maintains a CHANGELOG to help you easily understand what's happening.

via Git clone

git clone git://github.com/kahlan/kahlan.git
cd kahlan
composer install
bin/kahlan              # to run specs or,
bin/kahlan --coverage=4 # to run specs with coverage info for namespaces, classes & methods (require xdebug)

kahlan's People

Contributors

blueclock avatar ddziaduch avatar dm3yz avatar ezzatron avatar fabschurt avatar feryardiant avatar flashios09 avatar grafikart avatar jails avatar jarektkaczyk avatar jasonmccreary avatar jdreesen avatar khartir avatar kieranajp avatar liamjcrewe avatar lostkobrakai avatar lucasff avatar m1ome avatar michalfelski avatar mieszkomalawski avatar phishy avatar rlvillacarlos avatar rnijveld avatar samsonasik avatar shira-3749 avatar th3mouk avatar thattomperson avatar umpirsky avatar wms avatar yitznewton avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

kahlan's Issues

help function and coverage docs

I just struggled over an hour to get code coverage to run, as i always got this error:
Uncaught exception 'Exception' with message 'Unexisting path 'src'.' in /tmp/kahlan/var/www/live/site/vendor/crysalead/dir/src/Dir.php:91

After looking at the erroring file and digging this repo I eventually found the help output of the cli, which listed the src argument, which I needed to change. It would be really nice if you could make the exception a bit more descriptive, like "Couldn't find the supplied root folder 'src'." and maybe enhance the docs around the coverage function with a note, that the src argument must be correctly set.

Additionally, the help functionality does not work for me in the console. kahlan --help does simply output the same as without argument.

How to provide data to multiple it() ?

I would like to test something with multiple data in multiple describe() sections. I tried to define the data in a before(), but this does not work because it has not yet run when the for-loop is executed.

I tried to wrap the for-loop in a context() and suspect that I might give a third parameter like "parent", but this didn't work either and I could not find documentation on possible values for this third parameter.

What would be a good way to iterate over an array of data and potentially reuse this data set across multiple describe() ?

<?php
describe("My test", function () {

    before(function () {
        $this->things = [
            'thing:1',
            'thing:2',
            'thing:3',
        ];
    });

    describe("frontend test", function () {

        for ($this->things as $thing) {
            it("tests the thing {$thing} in frontend", function () use ($thing) {
                expect($thing)->toContain('thing:');
            });
        }

    });

    describe("backend test", function () {

        for ($this->things as $thing) {
            it("tests the thing {$thing} in backend", function () use ($thing) {
                expect($thing)->toContain('thing:');
            });
        }

    });

});
?>

How to create Interceptor class?

if you want to make it work with your own autoloader, you will need to create your own Interceptor class for it

Is there any how-to documentation or samples?

Type hinting is broken

Somewhere since 1.1.8 version expect() function became a global wrapper function without any docs (src/init.php:65) and so type hinting (in phpStorm) that worked properly before now is broken and so autocomplete too.
Is it possible to fix that?

Stubs don't seem to get cleared (conflicting tests)

Hello

I have two tests in my project that conflict (reduced down to the simplest case that causes the bug):

PingerSpec.php

<?php
use kahlan\plugin\Stub;

describe("Pinger", function() {
        beforeEach(function() {
            $this->describer = Stub::create(['class' => 'denzel\services\aws\InstanceDescriber']);
        });

        it("...", function() {
            expect(1)->toBe(1);
        });
});
?>

InstanceDescriberSpec.php

<?php
use denzel\services\aws\InstanceDescriber;
use kahlan\plugin\Stub;

describe("InstanceDescriber", function() {
    beforeEach(function() {
        $this->client = Stub::create(['class' => 'Aws\Ec2\Ec2Client']);
        $this->sut = new InstanceDescriber($this->client);
    });

    describe('getInstance()', function() {
        it("returns an instance", function() {
            expect(1)->toBe(1);
        });
    });
});
?>

If I run either test in isolation using fdescribe, each test works fine. If I run them together, though, I get:

.E                                                                               100%

Pinger
  ping()
    ✘ it ...
      an uncaught exception has been thrown in `/tmp/kahlan/denzel/services/aws/InstanceDescriber.php` line 21

      message:`kahlan\PhpErrorException` Code(0) with message "`E_RECOVERABLE_ERROR` Argument 1 passed to denzel\\services\\aws\\InstanceDescriber::__construct() must be an instance of Aws\\Ec2\\Ec2Client, none given, called in /opt/denzel/libraries/crysalead/kahlan/src/plugin/Stub.php on line 196 and defined"

        [NA] - /tmp/kahlan/opt/denzel/denzel/services/aws/InstanceDescriber.php, line  to 21
        denzel\services\aws\InstanceDescriber::__construct() - /opt/denzel/libraries/crysalead/kahlan/src/plugin/Stub.php, line 196
        kahlan\plugin\Stub::create() - /opt/denzel/spec/services/aws/PingerSpec.php, line 7
        kahlan\Suite::runCallbacks() - /opt/denzel/libraries/crysalead/kahlan/src/Spec.php, line 88
        kahlan\Spec::_specStart() - /opt/denzel/libraries/crysalead/kahlan/src/Spec.php, line 67
        kahlan\Spec::process() - /opt/denzel/libraries/crysalead/kahlan/src/Suite.php, line 304
        kahlan\Suite::process() - /opt/denzel/libraries/crysalead/kahlan/src/Suite.php, line 304
        kahlan\Suite::process() - /opt/denzel/libraries/crysalead/kahlan/src/Suite.php, line 304
        kahlan\Suite::process() - /opt/denzel/libraries/crysalead/kahlan/src/Suite.php, line 417
        kahlan\Suite::run() - /opt/denzel/libraries/crysalead/kahlan/src/cli/Kahlan.php, line 540
        filter\Filter::on() - /opt/denzel/libraries/crysalead/kahlan/src/cli/Kahlan.php, line 541
        kahlan\cli\Kahlan::_run() - /opt/denzel/libraries/crysalead/kahlan/src/cli/Kahlan.php, line 361
        filter\Filter::on() - /opt/denzel/libraries/crysalead/kahlan/src/cli/Kahlan.php, line 369
        kahlan\cli\Kahlan::run() - /opt/denzel/libraries/crysalead/kahlan/bin/kahlan, line 44

Executed 1 of 2 FAIL (EXCEPTION: 1) in 0.103 seconds

This suggests to me that the mock on InstanceDescriber in PingerSpec causes problems when it comes to InstanceDescriberSpec.

Am I approaching this incorrectly?

My classes (simplified for reference):

<?php
namespace denzel\services\aws;

use Aws\Ec2\Ec2Client;

class InstanceDescriber {

    protected $_client;

    public function __construct(Ec2Client $client) {
        $this->_client = $client;
    }

    public function getInstance($id) {
        $results = $this->_client->describeInstances(['InstanceIds' => [$id]]);
        return $results['Reservations'][0]['Instances'][0];
    }
}
?>
<?php
namespace denzel\services\aws;

class Pinger {
    protected $_describer;

    public function __construct(InstanceDescriber $describer) {
        $this->_describer = $describer;
    }
    public function ping($instanceId) {
        return false; // TODO
    }
}
?>

I'd appreciate any suggestions! :-)

[BUG] After last update something wrong with a Stub.

I have a stub:

Stub::on('Api\Models\AtmLog')->method('create', function($data=null, $whitelist=null) {
    return false;
});

Api\Models\AtmLog:

class ApiLog extends \Spec\Helper\BaseModel {
}

\Spec\Helper\BaseModel:

<?php
class BaseModel extends \Phalcon\Mvc\Model {

  public static function find($parameters=null){
    return parent::find($parameters);
  }

  public static function findFirst($parameters=null){
    return parent::findFirst($parameters);
  }

  public function save($data=null, $whiteList=null){
    return parent::save($data, $whiteList);
  }

  public function create($data=null, $whiteList=null){
    return parent::create($data, $whiteList);
  }

  public function delete() {
    return parent::delete();
  }
}

It correctly passes into create if i dump get_class($this) in create it will show Api\Models\ApiLog but Stub doesn't work. Maybe some BC was broken?

[dev] \plugin\Stub

In plugin\Stub.php we have a function _generateParameters. I have a question, there is a lines: https://github.com/crysalead/kahlan/blob/master/src/plugin/Stub.php#L479-L486

if ($parameter->isOptional()) {
    if ($parameter->isDefaultValueAvailable()) {
        $default = var_export($parameter->getDefaultValue(), true);
    } else {
        $default = 'null';
    }
    $default = ' = ' . preg_replace('/\s+/', '', $default);
}

I don't get, hot it could be optional and don't have a default value. Opposite situation i can handle, but i don't know any idea when should this line: https://github.com/crysalead/kahlan/blob/master/src/plugin/Stub.php#L483 have been fired.

Before and BeforeEach.

How can i control a chain which of this call first?
If there is some predicted flow i think it needed to be documented.

Stubbing exception does not work

With given method:

public function writeError($e)
{
    $this->log->errorln(sprintf(
        "FAILED\nReason: %s\nStacktrace:\n%s",
        $e->getMessage(), $e->getTraceAsString()
    ));
}

And given test:

    describe('->writeError()', function () {

        it('must use message and stack trace of exception', function () {
            /** @var \Exception $e */
            $e = Stub::create(['extends' => '\Exception', 'layer' => true]);
            $this->firstCalled = false;
            Stub::on($e)->method('getMessage', function () {
                $this->firstCalled = true;
                return '';
            });
            $this->secondCalled = false;
            Stub::on($e)->method('getTraceAsString', function () {
                $this->secondCalled = true;
                return '';
            });
            $job = new ExampleCronJob();
            $job->writeError($e);
            expect($this->firstCalled)->toBe(true);
            expect($this->secondCalled)->toBe(true);
        });

    });

It fails:

  ->writeError()
    ✘ it must use message and stack trace of exception
      expect->toBe() failed in `/.tests/spec/Cron/JobTest.php` line 113

      actual:
        (boolean) false
      expected:
        (boolean) true

  ->writeError()
    ✘ it must use message and stack trace of exception
      expect->toBe() failed in `/.tests/spec/Cron/JobTest.php` line 114

      actual:
        (boolean) false
      expected:
        (boolean) true

It seems that \Exception methods did not stubbed at all.

Stub Help

I'm having trouble getting stubs to work. I've had them working in other projects in the past. Here is a test spec I created:


use kahlan\plugin\Stub;

class MyClass {
    public function myMethod() {
        return 1;
    }
}

describe("Example", function() {

    it("stubs a method", function() {
        $instance = new MyClass();
        Stub::on($instance)->method('myMethod')->andReturn('Good Morning World!');
        expect($instance->myMethod())->toBe('Good Morning World!');
    });

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

    it("passes if false !== true", function() {
        expect(false)->not->toBe(true);
    });
});

The integer 1 is still being returned despite the fact that I'm stubbing the method right before. I'm on version 1.1.8. The stub test is failing, but the others are passing. There are no errors. This is what Kahlan returns:

  /\ /\__ _| |__ | | __ _ _ __
 / //_/ _` | '_ \| |/ _` | '_ \
/ __ \ (_| | | | | | (_| | | | |
\/  \/\__,_|_| |_|_|\__,_|_| |_|

The Unit/BDD PHP Test Framework for Freedom, Truth, and Justice.

Working Directory: /Users/kpalko/PhpstormProjects/ee-intl

Example
  ✘ it stubs a method
    expect->toBe() failed in `/spec/exporter/Csv/Test_Spec.php` line 16

    actual:
      (integer) 1
    expected:
      (string) "Good Morning World!"

  ✔ it passes if true === true
  ✔ it passes if false !== true


Executed 2 of 3 FAIL (FAILURE: 1) in 0.025 seconds

I'm sure I'm missing something silly. Any help would be greatly appreciated!

1.0.6 testing

After update dev to 1.0.6 i have a following message on test run:

actual: (string) "<?php \$__KMONKEY__3 = \\kahlan\\plugin\\Monkey::patched(__NAMESPACE__ , 'space\\MyClass2', false); ?><?php \$__KMONKEY__2 = \\kahlan\\plugin\\Monkey::patched(__NAMESPACE__ , 'MyAlias', false); ?><?php \$__KMONKEY__1 = \\kahlan\\plugin\\Monkey::patched(__NAMESPACE__ , 'mt_rand', true); ?><?php \$__KMONKEY__0 = \\kahlan\\plugin\\Monkey::patched(__NAMESPACE__ , 'function_exists', true); ?><?php\nuse name\\space\\MyClass as MyAlias;\nuse name\\space as space;\n\nif (\$__KMONKEY__0('myfunction')) {\n    \$thatIsWeird = true;\n}\n\n\$rand = \$__KMONKEY__1();\nnew \$__KMONKEY__2;\nnew \$__KMONKEY__3();\n"
expected: (string) "<?php \$__KMONKEY__3 = \\kahlan\\plugin\\Monkey::patched(null, 'name\\space\\MyClass2'); ?><?php \$__KMONKEY__2 = \\kahlan\\plugin\\Monkey::patched(null, 'name\\space\\MyClass'); ?><?php \$__KMONKEY__1 = \\kahlan\\plugin\\Monkey::patched(__NAMESPACE__ , 'mt_rand', true); ?><?php \$__KMONKEY__0 = \\kahlan\\plugin\\Monkey::patched(__NAMESPACE__ , 'function_exists', true); ?><?php\nuse name\\space\\MyClass as MyAlias;\nuse name\\space as space;\n\nif (\$__KMONKEY__0('myfunction')) {\n    \$thatIsWeird = true;\n}\n\n\$rand = \$__KMONKEY__1();\nnew \$__KMONKEY__2;\nnew \$__KMONKEY__3();\n"
Description: toBe expected actual to be identical to expected (===).

It's strange that travis build seems to be ok.

[BUG] toThrow

If you traverse a toThrow matcher you will see that a closure will be called twice. It's a real bug, at my opinion.

Removing "minimum stability".

Maybe you should remove minimum stability flag?
As i could see every your package in kahlan dep could be released into some initial version like 1.0.0.

Maybe add some labels?

It's a good practice to add a labels.
Currently there is no "New feature request" and "Idea".
Maybe some other labels can be handy.

Issue with generators

I wanted to try out your library, but already on the fourth test I found an issue of compatibility when trying to create objects of the \League\Period\Period class.

I get the following error message: PHP Fatal error: Generators cannot return values using "return" … which points to the first line of this function in the class.

public function split($interval)
    {
        $startDate = $this->startDate;
        $interval = static::filterDateInterval($interval);
        do {
            $endDate = $startDate->add($interval);
            if ($endDate > $this->endDate) {
                $endDate = $this->endDate;
            }
            yield new static($startDate, $endDate);

            $startDate = $endDate;
        } while ($startDate < $this->endDate);
    }

It seems like some of the "money patching" functionality it using a return in the function.

Coverage.

When i am trying to run bin/kahlan --config="kahlan-config.travis.php" --coverage="kahlan\plugin\Call".

It outputs:

Coverage Summary
----------------

Unexisting namespace: `kahlan\plugin\Call`, coverage can't be generated.

It is a quite strange.

Method chains on expect()

It would be nice for readability to have an ability calling expect() methods as chains.

Example:

it('looks like this', function () {
    expect($this->items)
        ->not->toBeNull()
        ->toBeA('array')
        ->toBeEmpty();
});

it('instead of this', function () {
    expect($this->items)->not->toBeNull();
    expect($this->items)->toBeA('array');
    expect($this->items)->toBeEmpty();
});

constructor override

Tried to override __construct to empty function according to this:

https://github.com/crysalead/kahlan/blob/master/src/plugin/Stub.php#L185

but apparently it's irrelevant now, since it seems not to be used anywhere in the code.

However, using magicMethods => true solved the issue, so please review the comment and I guess it would be nice to include such example in the docs http://kahlan.readthedocs.org/en/latest/stubs/#class-stubbing.

Anyway thanks for the breath of fresh air in php testing!

Incorrect JIT behaviour with unusual PHP syntax

For situation like

it('falls', function () {
    $pdf = new \mPDF();
});

Kahlan outputs

PHP Parse error:  syntax error, unexpected '}' in /path/to/temp/dir/kahlan/project/vendor/mpdf/mpdf/mpdf.php on line 1981

If we look into this file at this line we will see:

    if ($this->bodyBackgroundColor) {
        $s .= 'q ' .$this->SetFColor($this->bodyBackgroundColor, true)."\n";
        if ($this->bodyBackgroundColor{}0==5) { // RGBa
            $s .= $this->SetAlpha($__KMONKEY__123($this->bodyBackgroundColor{4})/100, 'Normal', true, 'F')."\n";
        }
        else if ($this->bodyBackgroundColor{}0==6) {    // CMYKa
            $s .= $this->SetAlpha($__KMONKEY__124($this->bodyBackgroundColor{5})/100, 'Normal', true, 'F')."\n";
        }
        $s .= $__KMONKEY__125('%.3F %.3F %.3F %.3F re f Q', ($clx*_MPDFK), ($cly*_MPDFK),$clw*_MPDFK,$clh*_MPDFK)."\n";
    }

At the same time the original file is:

    if ($this->bodyBackgroundColor) {
        $s .= 'q ' .$this->SetFColor($this->bodyBackgroundColor, true)."\n";
        if ($this->bodyBackgroundColor{0}==5) { // RGBa
            $s .= $this->SetAlpha(ord($this->bodyBackgroundColor{4})/100, 'Normal', true, 'F')."\n";
        }
        else if ($this->bodyBackgroundColor{0}==6) {    // CMYKa
            $s .= $this->SetAlpha(ord($this->bodyBackgroundColor{5})/100, 'Normal', true, 'F')."\n";
        }
        $s .= sprintf('%.3F %.3F %.3F %.3F re f Q', ($clx*_MPDFK), ($cly*_MPDFK),$clw*_MPDFK,$clh*_MPDFK)."\n";
    }

The problem is in that string:

if ($this->bodyBackgroundColor{}0==5) { // RGBa

Version of Kahlan: 1.1.5 (d78b421)
Version of mPDF: v6.0.0 (a15743d030ce3b5b7be36c6e83f76589b27c3f2c)

Controlling error reporting level.

Is there a way to control error reporting level of invoked code? Better globally for all suites...

This code

describe('example', function () {
    it('produces notice exception', function () {
        error_reporting(E_ALL ^ E_NOTICE ^ E_STRICT);
        $arr = [];
        expect($arr['some'])->toBe(2);
    });
});

produces next output:

example
  ✘ it produces notice exception
    an uncaught exception has been thrown in `/spec/SomeSpec.php` line 12

    message:`kahlan\PhpErrorException` Code(0) with message "`E_NOTICE` Undefined index: some"

before() can't run in another spec for specifying object

For example, I have class:

class Foo
{
    public function __construct(Dependency $d)
    {
        $this->d = $d;
    }
}

and the Dependency class:

class Dependency
{
       public function __construct($a, $b, $c) {}
}

I created FooSpec for Foo class:

// FooSpec.php
describe('Foo', function() {

    before(function() {
        $this->dependency = Stub::create(['class' => Dependency::class]);
        $this->foo = new Foo($this->dependency);
    });

});

and for DependencySpec for Dependency class:

describe('Dependency', function() {

    before(function() {
        $this->dependency = new Dependency(1, 2, 3);
    });

});

I got error:

Executed 0 of 1 FAIL (EXCEPTION: 1) in 0.016 seconds

Stub in one spec affecting another spec testing the class that had been stubbed

I've been experiencing a problem with stubs of a class in one spec affecting another spec testing the class itself which has me wondering if I'm following best practices for writing specs. Consider the following code for two example spec files using Monolog purely for demonstration:

/* filename: spec/MonologStubbingSpec.php */
use kahlan\plugin\Stub;
describe('MonologStubbing', function () {
    it('has a dummy expectation', function () {
        $stubbed_logger = Stub::create(['class' => 'Monolog\Logger']);
        expect(true)->toBe(true);
    });
});
/* filename: spec/MonologUsingSpec.php */
describe('MonologUsing', function () {
    it('calls addRecord', function () {
        $logger = new Monolog\Logger('test');
        expect($logger)->toReceive('addRecord');
        $logger->addInfo('test message');
    });
});

Running the MonologUsing spec on its own passes but when the specs are run together, it fails. Debugging in the spec/MonologUsingSpec.php with a breakpoint after the call to addInfo reveals the following:
With focus
with focus
Without focus
without focus
I've managed to work around it by using 'extends' instead of 'class' but I would imagine that this is a common scenario. Is this not an intended use of kahlan? Or is there something I'm missing like forgetting something in a teardown?

Strange behaviour of coverage

In some cases coverage does not seem to work properly inside describe() section, only inside it().

Example:

class Error implements \API\Result
{

    ...

    public function __construct($code, $additional = [])
    {
        $this->code = $code;
        $this->status = self::getStatus($code);
        $this->description = self::getDescription($code);
        $this->additional = $additional;
    }

    ...

    public function writeHeaders()
    {
        http_response_code($this->status);
        if (!empty($this->headers)) {
            foreach ($this->headers as $name => $value) {
                header($name.': '.$value, true);
            }
        }
    }

    ...

}

Now, next test passes OK.

    describe('new Result\Error(5)', function () {

        $err = new Error(5);

        it('must have code, status and description', function () use ($err) {
            expect($err->code)->not->toBeNull();
            expect($err->status)->not->toBeNull();
            expect($err->description)->not->toBeNull();
        });

        it('must have valid types of code, status and description',
            function () use ($err) {
                expect($err->code)->toBeA('int');
                expect($err->status)->toBeA('int');
                expect($err->description)->toBeA('string');
            }
        );

        it('must have empty additional parameters', function () use ($err) {
            expect($err->additional)->toBe([]);
        });

    });

And running kahlan with --coverage=4 prints:

Lines: 100.00% (4/4)               API\Result\Error::__construct()

But...next test case:

    describe('->writeHeaders()', function () {

        $printedHeaders = ['Allowed' => ['old header']];
        $printedHttpStatus = null;
        $header =
            function ($str, $rpl = null, $code = null) use (&$printedHeaders) {
                $name = explode(':', $str)[0];
                if ($rpl) {
                    $printedHeaders[$name] = [$str];
                } else {
                    $printedHeaders[$name][] = $str;
                }
            };
        $http_response_code =
            function ($code) use (&$printedHttpStatus) {
                $printedHttpStatus = $code;
                return $code;
            };
        $err = (new Error(5))->setHeaders([
            'Allowed' => 'nothing',
            'SecondHeader' => 'some',
        ]);

        Monkey::patch('header', $header);
        Monkey::patch('http_response_code', $http_response_code);
        $err->writeHeaders();


        it('must set correct response status',
            function () use (
                $err, &$printedHttpStatus
            ) {
                expect($printedHttpStatus)
                    ->not->toBeNull()
                    ->toBe($err->status);
            }
        );

        it('must set headers correctly',
            function () use ($err, &$printedHeaders) {
                expect($printedHeaders)->toHaveLength(2);
                foreach ($err->headers as $name => $val) {
                    expect($printedHeaders[$name][0])->toBe($name.': '.$val);
                }
            }
        );

        it('must overwrite existing headers',
            function () use (&$printedHeaders) {
                expect($printedHeaders['Allowed'])->toHaveLength(1);
            }
        );

    });

Passes OK and prints:

Lines: 0.00%   (0/2)               API\Result\Error::writeHeaders()

If we move monkey patching and method invoking inside it() section like this:

    describe('->writeHeaders()', function () {

        $printedHeaders = ['Allowed' => ['old header']];
        $printedHttpStatus = null;
        $header =
            function ($str, $rpl = null, $code = null) use (&$printedHeaders) {
                $name = explode(':', $str)[0];
                if ($rpl) {
                    $printedHeaders[$name] = [$str];
                } else {
                    $printedHeaders[$name][] = $str;
                }
            };
        $http_response_code =
            function ($code) use (&$printedHttpStatus) {
                $printedHttpStatus = $code;
                return $code;
            };
        $err = (new Error(5))->setHeaders([
            'Allowed' => 'nothing',
            'SecondHeader' => 'some',
        ]);

        it('must set correct response status',
            function () use (
                $err, &$printedHttpStatus, $header, $http_response_code
            ) {
                Monkey::patch('header', $header);
                Monkey::patch('http_response_code', $http_response_code);
                $err->writeHeaders();
                expect($printedHttpStatus)
                    ->not->toBeNull()
                    ->toBe($err->status);
            }
        );

        it('must set headers correctly',
            function () use ($err, &$printedHeaders) {
                expect($printedHeaders)->toHaveLength(2);
                foreach ($err->headers as $name => $val) {
                    expect($printedHeaders[$name][0])->toBe($name.': '.$val);
                }
            }
        );

        it('must overwrite existing headers',
            function () use (&$printedHeaders) {
                expect($printedHeaders['Allowed'])->toHaveLength(1);
            }
        );

    });

Then coverage works:

Lines: 100.00% (2/2)               API\Result\Error::writeHeaders()

[dev] Problem with Dot reporter

Maybe you saw this on dev branch, if so, just close this one.

Working Directory: /vagrant/crm/kahlan.dev/kahlan

................................................................................ 14%
................................................................................ 36%
................................................................................ 70%
................................................................................ 98%
................................................................................ 111%
................................................................................ 137%
................................................................................ 155%
................................................................................ 166%
............SS.S................................................................ 186%
................................................................................ 197%
............                                                                     200%

Need some help.

I am trying to override a Phalcon model based method "find, findFirst" and e.t.c.
They are inside c-extension. Could them be override over Monkey patching ?

Or there is some way to do this overload default model namespace through Interceptor?

Expect matchers chaining.

  1. It's nothing said about chaining in documentation, and it's kinda bad.
  2. If you look on Leo it's bit more eye-candy when you write it.

In example:

Kahlan

$a = [1, 2, 3];
expect($a)->toBeA('array');
expect($a)->toContain(2)->toContain(3);

Leo

$a = [1, 2, 3];
expect($a)->to->be->a('array')->and->to->contain([2, 3]);

It's more human-readable i think

Monkey patcher does not ignore control structure with `and` or `or` statements in some cases.

The bug was found trying to stub methods of PHPMailer class of PHPMailer library.

When new \PHPMailer() was mentioned inside describe kahlan started to fall with error:

PHP Parse error:  syntax error, unexpected '$__KMONKEY__100' (T_VARIABLE) in /path/to/temp/projects/project/vendor/phpmailer/phpmailer/class.phpmailer.php on line 1347

If we look into patched code at that line (1347) we will see:

if ('ssl' == $hostinfo[2] $__KMONKEY__100 ('' == $hostinfo[2] and 'ssl' == $this->SMTPSecure)) {

while original code is:

if ('ssl' == $hostinfo[2] or ('' == $hostinfo[2] and 'ssl' == $this->SMTPSecure)) {

After changing or to || this place is patched ok. But have a similar issue on line 1576:

if ($qp_mode $__KMONKEY__117 ($__KMONKEY__118($word) > $length)) {

while original code is:

if ($qp_mode and (strlen($word) > $length)) {

After changing and to && at this place all file is patched ok and tests run normally.

I seems that issue appears when and and or statements are used together with parentheses.

Version of kahlan: dev-master (cd04a55b27df999ede3110dffbeb9a6f9561869f)
Version of PHPMailer: v5.2.10 (07005ecbb80d11ec8c0f067bb37e8909cc7fcbb7)

Matcher `->toContainKey()`

It would be nice for readability to have a matcher ->toContainKey() for arrays...like that:

it('has required key', function () {
    expect(['name' => 'John', 'email' => '[email protected]'])->toContainKey('name');
});

in addition to current possible ways:

it('has required key', function () {
    expect(array_keys(['name' => 'John', 'email' => '[email protected]']))->toContain('name');
});

it('has required key', function () {
    $arr = ['name' => 'John', 'email' => '[email protected]'];
    expect(isset($arr['name']))->toBe(true);
});

Need improvements in functionality.

Currently Kahlan need some improvements in functionality:

  • Add a command to check up current installed kahlan version
  • Add a clear-cache command (to run up a clear run on Kahlan)
  • Drop cache after new version (maybe some workaround after composer update?)

Monkey patching of cURL

It seems that monkey patching does not work on curl_exec().

Example:

    it('does not monkey patch curl', function () {
        $curl = curl_init('http://google.com');
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
        Monkey::patch('curl_exec', function ($ch) {
            return 'expected';
        });
        expect(curl_exec($curl))->toBe('expected');
    });

Output:

  ✘ it does not monkey patch curl
    expect->toBe() failed in `/.tests/spec/curlTest.php` line 19

    actual:
      (string) "<HTML><HEAD><meta http-equiv=\"content-type\" content=\"text/html;charset=utf-8\">\n<TITLE>302 Moved</TITLE></HEAD><BODY>\n<H1>302 Moved</H1>\nThe document has moved\n<A HREF=\"http://www.google.bg/?gfe_rd=cr&amp;ei=f3IZVb_lNcaz8wf6v4LYAg\">here</A>.\r\n</BODY></HTML>\r\n"
    expected:
      (string) "expected"

Matcher ->toBeAn() as a copy of ->toBeA()

Is it possible to implement matcher ->toBeAn() as a copy of ->toBeA() in the same way as we have ->toContainKey() and ->toContainKeys()?

Just for situations like:

expect($some)->toBeAn('int');
expect($some)->toBeAn('array');

Grammar nazi doesn't let me sleep well.

Running on windows fails

I am running WIndows 10. Release: 1.2.0 - Have not tried other branches.

Warning: mkdir(): Invalid argument in C:\Users\MyUserName\AppData\Roaming\Composer\vendor\crysalead\jit\src\Interceptor.php on line 436

Warning: file_put_contents(C:\Users\MyUserName\AppData\Local\Temp\kahlan\C:\Users\MyUserName\AppData\Roaming\Composer\vendor/crysalead/dir/src\Dir.php): failed to open stream: Invalid argument in C:\Users\MyUserName\AppData\Roaming\Composer\vendor\crysalead\jit\src\Interceptor.php on line 438

Warning: touch(): Unable to create file C:\Users\MyUserName\AppData\Local\Temp\kahlan\C:\Users\MyUserName\AppData\Roaming\Composer\vendor/crysalead/dir/src\Dir.php because Invalid argument in C:\Users\MyUserName\AppData\Roaming\Composer\vendor\crysalead\jit\src\Interceptor.php on line 440

Warning: require(C:\Users\MyUserName\AppData\Local\Temp\kahlan\C:\Users\MyUserName\AppData\Roaming\Composer\vendor/crysalead/dir/src\Dir.php): failed to open stream: Invalid argument in C:\Users\MyUserName\AppData\Roaming\Composer\vendor\crysalead\jit\src\Interceptor.php on line 401

Fatal error: require(): Failed opening required 'C:\Users\MyUserName\AppData\Local\Temp\kahlan\C:\Users\MyUserName\AppData\Roaming\Composer\vendor/crysalead/dir/src\Dir.php' (include_path='C:\Users\MyUserName\AppData\Roaming\Composer\vendor/phpunit/php-text-template;C:\Users\MyUserName\AppData\Roaming\Composer\vendor/phpunit/php-timer;C:\Users\MyUserName\AppData\Roaming\Composer\vendor/phpunit/php-file-iterator;.;C:\php\pear') in C:\Users\MyUserName\AppData\Roaming\Composer\vendor\crysalead\jit\src\Interceptor.php on line 401

unexpected toContainKey() matcher result with null value

I expect the second test to pass. Don't you ?

$x = array ( 'product' => null);
 // pass, the key exists
expect(array_key_exists('product', $x))->toBe(true);
// fails despite array key exists, despite array_key_exists() returns true
expect($x)->toContainKey('product');

The JIT conflicts with PHP-VCR

When excluding the VCR namespace like the following:

use filter\Filter;

Filter::register('exclude.namespaces', function ($chain) {
    $defaults = ['VCR', 'Assert'];
    $excluded = $this->args()->get('exclude');
    $this->args()->set('exclude', array_unique(array_merge($excluded, $defaults)));
    return $chain->next();
});

Filter::apply($this, 'interceptor', 'exclude.namespaces');

A conflict still persist and fixtures files are not able to be loaded/saved.

JIT and control structures with `AND` or `OR` cases

Sorry for annoying, but here I am again.

Issue is totally similar to #64. The only difference is that keywords are in upper case:

if(($this->FontFamily == $family) AND ($this->FontStyle == $style) AND ($this->FontSizePt == $size) && !$forcewrite) {
    return $family;
}

is transformed to

if(($this->FontFamily == $family) $__KMONKEY__1174 ($this->FontStyle == $style) $__KMONKEY__1174 ($this->FontSizePt == $size) && !$forcewrite) {
    return $family;
}

Same mPDF library.

Version of Kahlan: 1.1.6 (6f28e24)
Version of mPDF: v6.0.0 (a15743d030ce3b5b7be36c6e83f76589b27c3f2c)

Question about Stub.

Hello, i have a question about stub. Let's pretend i have a controller similar to:

$client = new Client();

if (!$client->save()) {
    throw new Exception("Some strange exception");
}

if ($client->age > 30) {
    $client->is_overaged = true;
    if (!$client->save()){
         throw new Exception("Some second strange exception");
    }
}

How can i stub second exception? It's a second call of ->save() function.

Kahlan site.

Some projects like Peridot have they own good-looking site, and it's kinda helpful to have such one, a lots of new people may just use Peridot, because of their better documentation and good-looking lifted-up site. What do you think, about this?

[BUG/ISSUE] Before and BeforeEach.

I have a problem if i do something like that:

describe("Top level", function() {

    beforeEach(function() {
        $this->a = 10;
    });

    describe("Sub level", function() {

        before(function() {
              $this->b = 100;
        });    

        iit("should do something", function() {
             var_dump($this->a);
        });

    });

});

And it will say something like Undefined variable and throw Exception.
It's seems like iit working bad with nested scopes?

more verbose error in before()

when I have class :

class AClass
{
         public function __construct($a) {}
}

in the spec, I have :

describe('AClass', function() {

    before(function() {
        $this->object = new AClass();
    });

});

I got error:

Executed 0 of 2 FAIL (EXCEPTION: 2) in 0.018 seconds

I think it need more verbose error, so we can know what actually the error is.

Matcher ->toContain() for strings

Is it possible to implement version of ->toContain() matcher for strings?
In same way as we have:

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

It seems to be nice for redability to have something like that:

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

Scope injection.

Sometimes it's real handy to have pre-injected variables in all define().
I think it can be implemented through filter and some new run-level.
And then when scopes fired inject all variables of filter in this scope.
What do you think?

I'am talking about something like

// Config
Filter::register("app.helpers", function() {
   $scope = $this->scope();

   $helper = new \My\Namespace\Helper();
   $scope->inject($helper);
});


// Spec
describe("My Spec", function() {
    before(function() {
        $this->helper->do_some_action();
    });
});

PHP Warning: preg_replace(): No ending delimiter '~' found

When i generate coverage clover, this error happen.

PHP Warning:  preg_replace(): No ending delimiter '~' found in vendor\crysalead\kahlan\src\reporter\coverage\Collector.php on line 290
PHP Stack trace:
PHP   1. {main}() vendor\crysalead\kahlan\bin\kahlan:0
PHP   2. kahlan\cli\Kahlan->run() vendor\crysalead\kahlan\bin\kahlan:44
PHP   3. filter\Filter::on() vendor\crysalead\kahlan\src\cli\Kahlan.php:369
PHP   4. call_user_func_array() vendor\crysalead\filter\src\Filter.php:215
PHP   5. kahlan\cli\Kahlan->kahlan\cli\{closure}() vendor\crysalead\filter\src\Filter.php:215
PHP   6. kahlan\cli\Kahlan->_reporting() vendor\crysalead\kahlan\src\cli\Kahlan.php:363
PHP   7. filter\Filter::on() vendor\crysalead\kahlan\src\cli\Kahlan.php:558
PHP   8. call_user_func_array() vendor\crysalead\filter\src\Filter.php:215
PHP   9. kahlan\cli\Kahlan->{closure:kahlan-config.php:47-59}() vendor\crysalead\filter\src\Filter.php:215
PHP  10. kahlan\reporter\coverage\exporter\Clover::write() kahlan-config.php:57
PHP  11. kahlan\reporter\coverage\exporter\Clover::export() vendor\crysalead\kahlan\src\reporter\coverage\exporter\Clover.php:27
PHP  12. kahlan\reporter\Coverage->export() vendor\crysalead\kahlan\src\reporter\coverage\exporter\Clover.php:62
PHP  13. kahlan\reporter\coverage\Collector->export() vendor\crysalead\kahlan\src\reporter\Coverage.php:141
PHP  14. preg_replace() vendor\crysalead\kahlan\src\reporter\coverage\Collector.php:290
php -v
PHP 5.5.8 (cli) (built: Jan  8 2014 15:32:25)
Copyright (c) 1997-2013 The PHP Group
Zend Engine v2.5.0, Copyright (c) 1998-2013 Zend Technologies

Kahlan 1.1.8

Сode coverage report in HTML format?

There is a possibility that there will be native support for generating code coverage report in HTML format?

If not, can you give a tip how to achieve this?

P.S. You have made a great tool, thank you! Already using it in my projects.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.