Compare commits

...

111 Commits

Author SHA1 Message Date
e83667a20b docs: update changelog 2021-08-03 14:01:53 +01:00
aa96d75fb9 Merge pull request #364 from pestphp/bound-datasets
Datasets can now access the test case
2021-08-03 13:58:35 +01:00
57161ba5ad Merges master 2021-08-02 14:03:33 +01:00
b1a9254fc1 Merge branch 'master' into bound-datasets
# Conflicts:
#	tests/.snapshots/success.txt
2021-08-02 14:02:49 +01:00
e56e818659 Splits 8.1 OS jobs 2021-08-02 13:06:41 +01:00
79de0d5875 Splits 8.1 OS jobs 2021-08-02 13:05:55 +01:00
f6f8140ebc Splits 8.1 OS jobs 2021-08-02 13:05:29 +01:00
eed221af46 Adds continue-on-error for php 8.1 2021-08-02 13:03:13 +01:00
524457a4e6 Adds continue-on-error for php 8.1 2021-08-02 13:00:07 +01:00
8e289b7a7d Adds continue-on-error for php 8.1 2021-08-02 12:59:07 +01:00
172b69cf15 Adds continue-on-error for php 8.1 2021-08-02 12:57:57 +01:00
475279a4fa Adds continue-on-error for php 8.1 2021-08-02 12:56:29 +01:00
4b236bf9ff docs: update changelog 2021-08-02 12:50:44 +01:00
50e2d10029 docs: update changelog 2021-08-02 12:50:28 +01:00
fa8a57f1ab Merge pull request #350 from pestphp/teamcity-styling
Teamcity styling
2021-08-02 12:47:54 +01:00
715f8b420b Updates phpunit dependency version 2021-08-02 12:41:23 +01:00
a0637d86ff Refactors 2021-08-01 17:00:01 +01:00
1a941d7f92 Refactors 2021-08-01 16:45:06 +01:00
b5959aa3fa Refactors 2021-08-01 15:42:52 +01:00
8861dd2401 Merges with master 2021-08-01 15:40:34 +01:00
da73f4b395 Merge branch 'master' into teamcity-styling
# Conflicts:
#	tests/.snapshots/success.txt
2021-08-01 15:36:53 +01:00
d5097d0fe5 Merge conflicts 2021-07-31 23:28:23 +01:00
022ad4be0d Merge branch 'master' into bound-datasets
# Conflicts:
#	tests/.snapshots/success.txt
2021-07-31 23:27:38 +01:00
2d2a83e9e8 chore: run tests against php 8.1 2021-07-31 22:56:33 +01:00
67c7bee4fa Merge pull request #365 from def-studio/expect-toContain-with-multiple-needles
implements multiple needles in expect()->toContain()
2021-07-31 20:34:33 +01:00
675b0f1ec8 implements multiple needles in expect()->toContain() 2021-07-31 18:29:03 +02:00
c2b86c3ab3 Removes pest from stack traces 2021-07-30 16:34:30 +01:00
46337b8085 Removes pest from stack traces 2021-07-30 16:32:00 +01:00
df172d8eed Adds new lines 2021-07-30 15:36:38 +01:00
24b9160b79 Removes new line from event printing 2021-07-30 15:34:31 +01:00
a7860b0b8e CS 2021-07-30 12:41:57 +01:00
7471c224fa Output improvements. 2021-07-30 12:39:20 +01:00
2996135155 Output improvements. 2021-07-30 12:38:13 +01:00
1abab8d440 Output improvements. 2021-07-30 12:36:52 +01:00
e52c83e5be Output improvements. 2021-07-30 12:35:20 +01:00
5ed4545737 Output improvements. 2021-07-30 12:34:10 +01:00
8b39df68ce Output improvements. 2021-07-30 12:32:27 +01:00
04d8a3762b Output improvements. 2021-07-30 12:31:03 +01:00
0f7c8d00d6 Output improvements. 2021-07-30 12:29:39 +01:00
12fb4f8639 Output improvements. 2021-07-30 12:24:23 +01:00
6a84b825e6 Output improvements. 2021-07-30 12:23:24 +01:00
e8595c56b3 Merge branch 'master' into teamcity-styling 2021-07-30 11:46:57 +01:00
4a9fb2fa74 Snapshot update 2021-07-28 10:41:47 +01:00
d8fae6d689 Datasets can now access the test case and are executed after the setup method has run. 2021-07-28 10:39:39 +01:00
252f9a0e46 Merge pull request #321 from jordanbrauer/fix-missing-dataset-errors
Add user-friendly exception message for missing test inputs
2021-07-28 09:57:39 +01:00
8d24b4a217 chore: replace prop set/check with method call 2021-07-27 23:39:26 -05:00
43920f79a9 feat: add count method for checking message type presence 2021-07-27 23:36:09 -05:00
55bfc5856b Merge branch 'master' into fix-missing-dataset-errors 2021-07-27 23:02:12 -05:00
cd9d4acbc2 release: v1.13.0 2021-07-28 02:03:24 +01:00
2b5355419a chore: fixes types 2021-07-28 01:59:47 +01:00
22b822ce87 fix: skip with a false condition being ignored 2021-07-28 01:57:13 +01:00
b2c298b926 Merge pull request #363 from freekmurze/add-to-be-in
Add `toBeIn` expectation
2021-07-28 00:09:54 +01:00
671f3df115 fix tests 2021-07-28 01:00:19 +02:00
2dd77001b7 static analysis fix 2021-07-28 00:42:54 +02:00
5f574ded81 add toBeIn 2021-07-28 00:36:43 +02:00
6309e6818d Update README.md 2021-07-26 23:19:51 +01:00
4813ab6ffb Add sponsor 2021-07-26 23:18:55 +01:00
4ebba1298a release: v1.12.0 2021-07-26 22:46:22 +01:00
2e0d1bb5a0 Merge branch 'master' of https://github.com/pestphp/pest 2021-07-26 22:45:08 +01:00
09d2b16767 docs: updates changelog 2021-07-26 22:45:03 +01:00
f56556eb73 Merge pull request #357 from jordanbrauer/fix-static-hooks
Fix static hooks
2021-07-26 22:33:58 +01:00
f387ca8624 Merge pull request #353 from rezaamini-ir/master
Add --force flag to pest:test command
2021-07-26 22:27:08 +01:00
ca9d783cf9 chore: makes phpunit/phpunit requirement above ^9.3.7 2021-07-26 22:02:57 +01:00
863ddea50b style: split assignment into two lines clarity 2021-07-22 17:54:22 -05:00
00b4bb6305 Merge branch 'master' into fix-missing-dataset-errors 2021-07-21 21:26:33 -05:00
d217503a6a test: use consistent test descriptors 2021-07-21 20:47:48 -05:00
b6012862c4 style: run linter 2021-07-21 20:43:21 -05:00
60c0ad006f fix: prevent the global hooks from piling up (#351)
this happens due to the global `*All()` hooks (before & after) being
defined as static. We should be a good citizen and we need to clean up
our mess for the next person in the test instance constructor
2021-07-21 20:38:20 -05:00
79ff332afe test: global "all" hooks must only run once (#351)
in other words, they can only run once per file
2021-07-21 20:32:39 -05:00
595bbe32a4 Add bool to the condition 2021-07-22 01:41:12 +04:30
328427bfdb docs: update changelog 2021-07-21 12:59:45 +01:00
51f556799c Merge pull request #352 from pestphp/higher-order-callable-datasets
Higher order callable datasets
2021-07-21 12:04:35 +01:00
5e0a0855ea Add a better description 2021-07-21 14:38:11 +04:30
7ec3460d73 Add --force flag to pest:test command 2021-07-21 13:02:09 +04:30
4c8c42cd20 Refactor 2021-07-21 08:47:55 +01:00
09682dd393 Alters test to prove order doesn't matter 2021-07-21 08:16:54 +01:00
82c18d3848 Type fixes 2021-07-21 07:58:05 +01:00
3bdba9210d Adds another test 2021-07-21 07:44:05 +01:00
371620d161 Adds support for receiving datasets in higher order tests 2021-07-21 07:40:19 +01:00
d10281f851 Adds test cases for loggers and removes use of str_starts_with. 2021-07-19 12:59:18 +01:00
47ceb2419b Update README.md 2021-07-16 22:12:31 +01:00
771b5b2e53 Adds Spatie 2021-07-16 22:12:19 +01:00
05f72f9b6d Aligns test case name with test names 2021-07-16 17:59:07 +01:00
f9de1b9c00 Refactor 2021-07-16 17:51:12 +01:00
9516e56242 Improves test case name styling 2021-07-16 17:50:18 +01:00
5f315fc899 Updates warning color 2021-07-16 17:46:30 +01:00
e59606818d More work on output 2021-07-16 17:26:48 +01:00
e16104350e More work on output 2021-07-16 16:45:35 +01:00
df31191f4e Merge branch 'master' into teamcity-assertion-fix 2021-07-16 16:45:16 +01:00
5b310f6f93 More work on output 2021-07-16 15:58:44 +01:00
0200f90e9a Start of work on better TeamCity output 2021-07-16 15:06:10 +01:00
027e69e48f Merge pull request #349 from pestphp/teamcity-assertion-fix
Fixes the assertion output showing as 0 in the TeamCity logger
2021-07-16 14:58:30 +01:00
33e01e3805 Merge pull request #348 from jdanino/stubfix
Fix the unit test stub
2021-07-16 14:26:01 +01:00
13781dcd14 Fixes the assertion output showing as 0 in the TeamCity logger. 2021-07-16 13:17:33 +01:00
164bad437a Fixes the assertion output showing as 0 in the TeamCity logger. 2021-07-16 13:10:30 +01:00
7ddcc03ad9 Update stubs/Unit.php
Co-authored-by: Owen Voke <development@voke.dev>
2021-07-16 13:00:32 +01:00
bbe4445257 Fix the unit test stub 2021-07-16 12:40:57 +02:00
dc75b34deb refactor: move logic into boolean method in TestCaseFactory 2021-06-30 09:51:49 -05:00
8e22803797 Merge branch 'master' into fix-missing-dataset-errors 2021-06-30 09:04:20 -05:00
82bd836ae9 Merge branch 'master' into fix-missing-dataset-errors 2021-06-21 19:50:15 -05:00
e64856c664 test: use throws method instead of assert 2021-06-17 13:33:11 -05:00
de86598c0d Merge branch 'master' into fix-missing-dataset-errors 2021-06-16 21:36:42 -05:00
553b45306f feat: handle unions (PHP 8) 2021-06-16 21:29:08 -05:00
2e7192ab95 chore: static analysis adjustments - deprecation RE: ReflectionType::__toString 2021-06-16 14:42:51 -05:00
e21e3080e0 test: add new exception check & update snapshots 2021-06-16 14:04:50 -05:00
6a7ee90ff5 feat: throw user-friendly exception for missing argument data 2021-06-16 01:01:34 -05:00
9d66893d5a feat: add boolean property to signal dependency proxy calls 2021-06-16 01:00:23 -05:00
64e780cf72 fix: types 2021-06-16 00:58:46 -05:00
9bf141f698 style: formatting & linting 2021-06-16 00:54:22 -05:00
9904094590 feat: add new exception for missing datasets on tests with arguments 2021-06-16 00:53:17 -05:00
9e5b779abc feat: add helper function for mapping a function's arguments 2021-06-16 00:52:31 -05:00
32 changed files with 750 additions and 162 deletions

View File

@ -5,11 +5,26 @@ on: ['push', 'pull_request']
jobs:
ci:
runs-on: ${{ matrix.os }}
continue-on-error: ${{ matrix.experimental }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
php: ['7.3', '7.4', '8.0']
dependency-version: [prefer-lowest, prefer-stable]
experimental: [false]
include:
- php: '8.1'
os: ubuntu-latest
experimental: true
dependency-version: prefer-stable
- php: '8.1'
os: macos-latest
experimental: true
dependency-version: prefer-stable
- php: '8.1'
os: windows-latest
experimental: true
dependency-version: prefer-stable
name: PHP ${{ matrix.php }} - ${{ matrix.os }} - ${{ matrix.dependency-version }}

View File

@ -4,6 +4,36 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).
## [v1.14.0 (2021-08-03)](https://github.com/pestphp/pest/compare/v1.13.0...v1.14.0)
### Added
- A new bound closure that allows you to access the test case in Datasets ([#364](https://github.com/pestphp/pest/pull/364))
## [v1.13.0 (2021-08-02)](https://github.com/pestphp/pest/compare/v1.12.0...v1.13.0)
### Added
- A cleaner output when running the Pest runner in PhpStorm ([#350](https://github.com/pestphp/pest/pull/350))
- `toBeIn` expectation ([#363](https://github.com/pestphp/pest/pull/363))
### Fixed
- `skip` with false condition marking test as skipped ([22b822c](https://github.com/pestphp/pest/commit/22b822ce87a3d19d84960fa5c93eb286820b525d))
## [v1.12.0 (2021-07-26)](https://github.com/pestphp/pest/compare/v1.11.0...v1.12.0)
### Added
- `--force` option to override tests in `pest:test` artisan command ([#353](https://github.com/pestphp/pest/pull/353))
- Support for PHPUnit `^9.3.7` ([ca9d783](https://github.com/pestphp/pest/commit/ca9d783cf942a2caabc85ff7a728c7f28350c67a))
### Fixed
- `beforeAll` and `afterAll` behind called multiple times per test ([#357](https://github.com/pestphp/pest/pull/357))
## [v1.11.0 (2021-07-21)](https://github.com/pestphp/pest/compare/v1.10.0...v1.11.0)
### Added
- Support for interacting with datasets in higher order tests ([#352](https://github.com/pestphp/pest/pull/352))
### Changed
- The unit test stub now uses the expectation API ([#348](https://github.com/pestphp/pest/pull/348))
### Fixed
- PhpStorm will no longer show 0 assertions in the output ([#349](https://github.com/pestphp/pest/pull/349))
## [v1.10.0 (2021-07-12)](https://github.com/pestphp/pest/compare/v1.9.1...v1.10.0)
### Added
- The ability to use higher order expectations inside higher order tests ([#341](https://github.com/pestphp/pest/pull/341))

View File

@ -21,8 +21,10 @@ We would like to extend our thanks to the following sponsors for funding Pest de
### Premium Sponsors
- **[Scout APM](https://scoutapm.com)**
- **[Akaunting](https://akaunting.com)**
- **[Codecourse](https://codecourse.com/)**
- **[Meema](https://meema.io/)**
- **[Scout APM](https://scoutapm.com)**
- **[Spatie](https://spatie.be/)**
Pest was created by **[Nuno Maduro](https://twitter.com/enunomaduro)** under the **[Sponsorware license](https://github.com/sponsorware/docs)**. It got open-sourced and is now licensed under the **[MIT license](https://opensource.org/licenses/MIT)**.
Pest is an open-sourced software licensed under the **[MIT license](https://opensource.org/licenses/MIT)**.

View File

@ -20,7 +20,7 @@
"php": "^7.3 || ^8.0",
"nunomaduro/collision": "^5.4.0",
"pestphp/pest-plugin": "^1.0.0",
"phpunit/phpunit": ">= 9.3.7 <= 9.5.6"
"phpunit/phpunit": "^9.5.5"
},
"autoload": {
"psr-4": {
@ -57,7 +57,7 @@
"scripts": {
"lint": "php-cs-fixer fix -v",
"test:lint": "php-cs-fixer fix -v --dry-run",
"test:types": "phpstan analyse --ansi --memory-limit=0",
"test:types": "phpstan analyse --ansi --memory-limit=-1",
"test:unit": "php bin/pest --colors=always --exclude-group=integration",
"test:integration": "php bin/pest --colors=always --group=integration",
"update:snapshots": "REBUILD_SNAPSHOTS=true php bin/pest --colors=always",

View File

@ -30,7 +30,7 @@ final class AddsDefaults
}
if ($arguments[self::PRINTER] === \PHPUnit\Util\Log\TeamCity::class) {
$arguments[self::PRINTER] = new TeamCity($arguments['verbose'] ?? false, $arguments['colors'] ?? DefaultResultPrinter::COLOR_ALWAYS);
$arguments[self::PRINTER] = new TeamCity(null, $arguments['verbose'] ?? false, $arguments['colors'] ?? DefaultResultPrinter::COLOR_ALWAYS);
}
// Load our junit logger instead.

View File

@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace Pest\Concerns\Logging;
/**
* @internal
*/
trait WritesToConsole
{
private function writeSuccess(string $message): void
{
$this->writePestTestOutput($message, 'fg-green, bold', '✓');
}
private function writeError(string $message): void
{
$this->writePestTestOutput($message, 'fg-red, bold', '');
}
private function writeWarning(string $message): void
{
$this->writePestTestOutput($message, 'fg-yellow, bold', '-');
}
private function writePestTestOutput(string $message, string $color, string $symbol): void
{
$this->writeWithColor($color, "$symbol ", false);
$this->write($message);
$this->writeNewLine();
}
}

View File

@ -73,6 +73,8 @@ trait Testable
{
$this->__test = $test;
$this->__description = $description;
self::$beforeAll = null;
self::$afterAll = null;
parent::__construct('__test', $data);
}
@ -271,7 +273,19 @@ trait Testable
*/
public function __test()
{
return $this->__callClosure($this->__test, func_get_args());
return $this->__callClosure($this->__test, $this->resolveTestArguments(func_get_args()));
}
/**
* Resolve the passed arguments. Any Closures will be bound to the testcase and resolved.
*
* @throws Throwable
*/
private function resolveTestArguments(array $arguments): array
{
return array_map(function ($data) {
return $data instanceof Closure ? $this->__callClosure($data, []) : $data;
}, $arguments);
}
/**

View File

@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
namespace Pest\Exceptions;
use BadFunctionCallException;
use NunoMaduro\Collision\Contracts\RenderlessEditor;
use NunoMaduro\Collision\Contracts\RenderlessTrace;
use Symfony\Component\Console\Exception\ExceptionInterface;
/**
* Creates a new instance of dataset is not present for test that has arguments.
*
* @internal
*/
final class DatasetMissing extends BadFunctionCallException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
{
/**
* Create new exception instance.
*
* @param array<string, string> $args A map of argument names to their typee
*/
public function __construct(string $file, string $name, array $args)
{
parent::__construct(sprintf(
"A test with the description '%s' has %d argument(s) ([%s]) and no dataset(s) provided in %s",
$name,
count($args),
implode(', ', array_map(static function (string $arg, string $type): string {
return sprintf('%s $%s', $type, $arg);
}, array_keys($args), $args)),
$file,
));
}
}

View File

@ -266,14 +266,16 @@ final class Expectation
/**
* Asserts that $needle is an element of the value.
*
* @param mixed $needle
* @param mixed $needles
*/
public function toContain($needle): Expectation
public function toContain(...$needles): Expectation
{
if (is_string($this->value)) {
Assert::assertStringContainsString($needle, $this->value);
} else {
Assert::assertContains($needle, $this->value);
foreach ($needles as $needle) {
if (is_string($this->value)) {
Assert::assertStringContainsString($needle, $this->value);
} else {
Assert::assertContains($needle, $this->value);
}
}
return $this;
@ -371,6 +373,18 @@ final class Expectation
return $this;
}
/**
* Asserts that the value is one of the given values.
*
* @param iterable<int|string, mixed> $values
*/
public function toBeIn(iterable $values): Expectation
{
Assert::assertContains($this->value, $values);
return $this;
}
/**
* Asserts that the value is infinite.
*/

View File

@ -228,4 +228,13 @@ final class TestCaseFactory
return $classFQN;
}
/**
* Determine if the test case will receive argument input from Pest, or not.
*/
public function receivesArguments(): bool
{
return count($this->datasets) > 0
|| $this->factoryProxies->count('addDependencies') > 0;
}
}

View File

@ -21,7 +21,7 @@ final class PestTestCommand extends Command
*
* @var string
*/
protected $signature = 'pest:test {name : The name of the file} {--unit : Create a unit test} {--dusk : Create a Dusk test} {--test-directory=tests : The name of the tests directory}';
protected $signature = 'pest:test {name : The name of the file} {--unit : Create a unit test} {--dusk : Create a Dusk test} {--test-directory=tests : The name of the tests directory} {--force : Overwrite the existing test file with the same name}';
/**
* The console command description.
@ -56,7 +56,7 @@ final class PestTestCommand extends Command
File::makeDirectory(dirname($target), 0777, true, true);
}
if (File::exists($target)) {
if (File::exists($target) && !(bool) $this->option('force')) {
throw new InvalidConsoleArgument(sprintf('%s already exist', $target));
}

View File

@ -5,25 +5,34 @@ declare(strict_types=1);
namespace Pest\Logging;
use function getmypid;
use Pest\Concerns\Logging\WritesToConsole;
use Pest\Concerns\Testable;
use Pest\Support\ExceptionTrace;
use function Pest\version;
use PHPUnit\Framework\AssertionFailedError;
use PHPUnit\Framework\Test;
use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\TestResult;
use PHPUnit\Framework\TestSuite;
use PHPUnit\Framework\Warning;
use PHPUnit\TextUI\DefaultResultPrinter;
use function round;
use function str_replace;
use function strlen;
use Throwable;
final class TeamCity extends DefaultResultPrinter
{
use WritesToConsole;
private const PROTOCOL = 'pest_qn://';
private const NAME = 'name';
private const LOCATION_HINT = 'locationHint';
private const DURATION = 'duration';
private const TEST_SUITE_STARTED = 'testSuiteStarted';
private const TEST_SUITE_FINISHED = 'testSuiteFinished';
private const TEST_COUNT = 'testCount';
private const TEST_STARTED = 'testStarted';
private const TEST_FINISHED = 'testFinished';
/** @var int */
private $flowId;
@ -34,148 +43,105 @@ final class TeamCity extends DefaultResultPrinter
/** @var \PHPUnit\Util\Log\TeamCity */
private $phpunitTeamCity;
public function __construct(bool $verbose, string $colors)
/**
* @param resource|string|null $out
*/
public function __construct($out, bool $verbose, string $colors)
{
parent::__construct(null, $verbose, $colors, false, 80, false);
$this->phpunitTeamCity = new \PHPUnit\Util\Log\TeamCity(
null,
$verbose,
$colors,
false,
80,
false
);
parent::__construct($out, $verbose, $colors);
$this->phpunitTeamCity = new \PHPUnit\Util\Log\TeamCity($out, $verbose, $colors);
$this->logo();
}
private function logo(): void
{
$this->writeNewLine();
$this->write('Pest ' . version());
$this->writeNewLine();
}
public function printResult(TestResult $result): void
{
$this->printHeader($result);
$this->printFooter($result);
$this->write('Tests: ');
$results = [
'failed' => ['count' => $result->errorCount() + $result->failureCount(), 'color' => 'fg-red'],
'skipped' => ['count' => $result->skippedCount(), 'color' => 'fg-yellow'],
'warned' => ['count' => $result->warningCount(), 'color' => 'fg-yellow'],
'risked' => ['count' => $result->riskyCount(), 'color' => 'fg-yellow'],
'incomplete' => ['count' => $result->notImplementedCount(), 'color' => 'fg-yellow'],
'passed' => ['count' => $this->successfulTestCount($result), 'color' => 'fg-green'],
];
$filteredResults = array_filter($results, function ($item): bool {
return $item['count'] > 0;
});
foreach ($filteredResults as $key => $info) {
$this->writeWithColor($info['color'], $info['count'] . " $key", false);
if ($key !== array_reverse(array_keys($filteredResults))[0]) {
$this->write(', ');
}
}
$this->writeNewLine();
$this->write("Assertions: $this->numAssertions");
$this->writeNewLine();
$this->write("Time: {$result->time()}s");
$this->writeNewLine();
}
private function successfulTestCount(TestResult $result): int
{
return $result->count()
- $result->failureCount()
- $result->errorCount()
- $result->skippedCount()
- $result->warningCount()
- $result->notImplementedCount()
- $result->riskyCount();
}
/** @phpstan-ignore-next-line */
public function startTestSuite(TestSuite $suite): void
{
$suiteName = $suite->getName();
if (static::isCompoundTestSuite($suite)) {
$this->writeWithColor('bold', ' ' . $suiteName);
} elseif (static::isPestTestSuite($suite)) {
$this->writeWithColor('fg-white, bold', ' ' . substr_replace($suiteName, '', 0, 2) . ' ');
} else {
$this->writeWithColor('fg-white, bold', ' ' . $suiteName);
}
$this->writeNewLine();
$this->flowId = (int) getmypid();
if (!$this->isSummaryTestCountPrinted) {
$this->printEvent(
'testCount',
['count' => $suite->count()]
);
$this->printEvent(self::TEST_COUNT, [
'count' => $suite->count(),
]);
$this->isSummaryTestCountPrinted = true;
}
$suiteName = $suite->getName();
if (file_exists($suiteName) || !method_exists($suiteName, '__getFileName')) {
$this->printEvent(
self::TEST_SUITE_STARTED, [
self::NAME => $suiteName,
self::LOCATION_HINT => self::PROTOCOL . $suiteName,
]);
return;
}
$fileName = $suiteName::__getFileName();
$this->printEvent(
self::TEST_SUITE_STARTED, [
self::NAME => substr($suiteName, 2),
self::LOCATION_HINT => self::PROTOCOL . $fileName,
$this->printEvent(self::TEST_SUITE_STARTED, [
self::NAME => static::isCompoundTestSuite($suite) ? $suiteName : substr($suiteName, 2),
self::LOCATION_HINT => self::PROTOCOL . (static::isCompoundTestSuite($suite) ? $suiteName : $suiteName::__getFileName()),
]);
}
/** @phpstan-ignore-next-line */
public function endTestSuite(TestSuite $suite): void
{
$suiteName = $suite->getName();
if (file_exists($suiteName) || !method_exists($suiteName, '__getFileName')) {
$this->printEvent(
self::TEST_SUITE_FINISHED, [
self::NAME => $suiteName,
self::LOCATION_HINT => self::PROTOCOL . $suiteName,
]);
return;
}
$this->printEvent(
self::TEST_SUITE_FINISHED, [
self::NAME => substr($suiteName, 2),
]);
}
/**
* @param Test|Testable $test
*/
public function startTest(Test $test): void
{
if (!TeamCity::isPestTest($test)) {
$this->phpunitTeamCity->startTest($test);
return;
}
$this->printEvent('testStarted', [
self::NAME => $test->getName(),
// @phpstan-ignore-next-line
self::LOCATION_HINT => self::PROTOCOL . $test->toString(),
]);
}
/**
* @param Test|Testable $test
*/
public function endTest(Test $test, float $time): void
{
if (!TeamCity::isPestTest($test)) {
$this->phpunitTeamCity->endTest($test, $time);
return;
}
$this->printEvent('testFinished', [
self::NAME => $test->getName(),
self::DURATION => self::toMilliseconds($time),
]);
}
/**
* @param Test|Testable $test
*/
public function addError(Test $test, Throwable $t, float $time): void
{
$this->phpunitTeamCity->addError($test, $t, $time);
}
/**
* @phpstan-ignore-next-line
*
* @param Test|Testable $test
*/
public function addWarning(Test $test, Warning $e, float $time): void
{
$this->phpunitTeamCity->addWarning($test, $e, $time);
}
public function addFailure(Test $test, AssertionFailedError $e, float $time): void
{
$this->phpunitTeamCity->addFailure($test, $e, $time);
}
protected function writeProgress(string $progress): void
{
}
/**
* @param array<string, string|int> $params
*/
private function printEvent(string $eventName, array $params = []): void
{
$this->write("\n##teamcity[{$eventName}");
$this->write("##teamcity[{$eventName}");
if ($this->flowId !== 0) {
$params['flowId'] = $this->flowId;
@ -198,9 +164,56 @@ final class TeamCity extends DefaultResultPrinter
);
}
private static function toMilliseconds(float $time): int
/** @phpstan-ignore-next-line */
public function endTestSuite(TestSuite $suite): void
{
return (int) round($time * 1000);
$suiteName = $suite->getName();
$this->writeNewLine();
$this->writeNewLine();
$this->printEvent(self::TEST_SUITE_FINISHED, [
self::NAME => static::isCompoundTestSuite($suite) ? $suiteName : substr($suiteName, 2),
self::LOCATION_HINT => self::PROTOCOL . (static::isCompoundTestSuite($suite) ? $suiteName : $suiteName::__getFileName()),
]);
}
/**
* @param Test|Testable $test
*/
public function startTest(Test $test): void
{
if (!TeamCity::isPestTest($test)) {
$this->phpunitTeamCity->startTest($test);
return;
}
$this->printEvent(self::TEST_STARTED, [
self::NAME => $test->getName(),
// @phpstan-ignore-next-line
self::LOCATION_HINT => self::PROTOCOL . $test->toString(),
]);
}
/**
* Verify that the given test suite is a valid Pest suite.
*
* @param TestSuite<Test> $suite
*/
private static function isPestTestSuite(TestSuite $suite): bool
{
return strncmp($suite->getName(), 'P\\', strlen('P\\')) === 0;
}
/**
* Determine if the test suite is made up of multiple smaller test suites.
*
* @param TestSuite<Test> $suite
*/
private static function isCompoundTestSuite(TestSuite $suite): bool
{
return file_exists($suite->getName()) || !method_exists($suite->getName(), '__getFileName');
}
public static function isPestTest(Test $test): bool
@ -210,4 +223,75 @@ final class TeamCity extends DefaultResultPrinter
return in_array(Testable::class, $uses, true);
}
/**
* @param Test|Testable $test
*/
public function endTest(Test $test, float $time): void
{
$this->printEvent(self::TEST_FINISHED, [
self::NAME => $test->getName(),
self::DURATION => self::toMilliseconds($time),
]);
if (!$this->lastTestFailed) {
$this->writeSuccess($test->getName());
}
$this->numAssertions += $test instanceof TestCase ? $test->getNumAssertions() : 1;
$this->lastTestFailed = false;
}
private static function toMilliseconds(float $time): int
{
return (int) round($time * 1000);
}
public function addError(Test $test, Throwable $t, float $time): void
{
$this->markAsFailure($t);
$this->writeError($test->getName());
$this->phpunitTeamCity->addError($test, $t, $time);
}
public function addFailure(Test $test, AssertionFailedError $e, float $time): void
{
$this->markAsFailure($e);
$this->writeError($test->getName());
$this->phpunitTeamCity->addFailure($test, $e, $time);
}
public function addWarning(Test $test, Warning $e, float $time): void
{
$this->markAsFailure($e);
$this->writeWarning($test->getName());
$this->phpunitTeamCity->addWarning($test, $e, $time);
}
public function addIncompleteTest(Test $test, Throwable $t, float $time): void
{
$this->markAsFailure($t);
$this->writeWarning($test->getName());
$this->phpunitTeamCity->addIncompleteTest($test, $t, $time);
}
public function addRiskyTest(Test $test, Throwable $t, float $time): void
{
$this->markAsFailure($t);
$this->writeWarning($test->getName());
$this->phpunitTeamCity->addRiskyTest($test, $t, $time);
}
public function addSkippedTest(Test $test, Throwable $t, float $time): void
{
$this->markAsFailure($t);
$this->writeWarning($test->getName());
$this->phpunitTeamCity->addSkippedTest($test, $t, $time);
}
private function markAsFailure(Throwable $t): void
{
$this->lastTestFailed = true;
ExceptionTrace::removePestReferences($t);
}
}

View File

@ -148,6 +148,9 @@ final class TestCall
? $conditionOrMessage
: $message;
/** @var callable(): bool $condition */
$condition = $condition->bindTo(null);
$this->testCaseFactory
->chains
->addWhen($condition, Backtrace::file(), Backtrace::line(), 'markTestSkipped', [$message]);

View File

@ -6,7 +6,7 @@ namespace Pest;
function version(): string
{
return '1.10.0';
return '1.14.0';
}
function testDirectory(string $file = ''): string

View File

@ -5,11 +5,13 @@ declare(strict_types=1);
namespace Pest\Repositories;
use Closure;
use Pest\Exceptions\DatasetMissing;
use Pest\Exceptions\ShouldNotHappen;
use Pest\Exceptions\TestAlreadyExist;
use Pest\Exceptions\TestCaseAlreadyInUse;
use Pest\Exceptions\TestCaseClassOrTraitNotFound;
use Pest\Factories\TestCaseFactory;
use Pest\Support\Reflection;
use Pest\Support\Str;
use Pest\TestSuite;
use PHPUnit\Framework\TestCase;
@ -140,6 +142,14 @@ final class TestRepository
throw new TestAlreadyExist($test->filename, $test->description);
}
if (!$test->receivesArguments()) {
$arguments = Reflection::getFunctionArguments($test->test);
if (count($arguments) > 0) {
throw new DatasetMissing($test->filename, $test->description, $arguments);
}
}
$this->state[sprintf('%s%s%s', $test->filename, self::SEPARATOR, $test->description)] = $test;
}
}

View File

@ -5,6 +5,7 @@ declare(strict_types=1);
namespace Pest\Support;
use Closure;
use ReflectionProperty;
use Throwable;
/**
@ -36,4 +37,30 @@ final class ExceptionTrace
throw $throwable;
}
}
/**
* Removes any item from the stack trace referencing Pest so as not to
* crowd the error log for the end user.
*/
public static function removePestReferences(Throwable $t): void
{
if (!property_exists($t, 'serializableTrace')) {
return;
}
$property = new ReflectionProperty($t, 'serializableTrace');
$property->setAccessible(true);
$trace = $property->getValue($t);
$cleanedTrace = [];
foreach ($trace as $item) {
if (key_exists('file', $item) && mb_strpos($item['file'], 'vendor/pestphp/pest/') > 0) {
continue;
}
$cleanedTrace[] = $item;
}
$property->setValue($t, $cleanedTrace);
}
}

View File

@ -35,7 +35,7 @@ final class HigherOrderCallables
*/
public function expect($value)
{
return new Expectation($value instanceof Closure ? Reflection::bindCallable($value) : $value);
return new Expectation($value instanceof Closure ? Reflection::bindCallableWithData($value) : $value);
}
/**
@ -59,7 +59,7 @@ final class HigherOrderCallables
*/
public function tap(callable $callable)
{
Reflection::bindCallable($callable);
Reflection::bindCallableWithData($callable);
return $this->target;
}

View File

@ -53,4 +53,20 @@ final class HigherOrderMessageCollection
$message->call($target);
}
}
/**
* Count the number of messages with the given name.
*
* @param string $name A higher order message name (usually a method name)
*/
public function count(string $name): int
{
return array_reduce(
$this->messages,
static function (int $total, HigherOrderMessage $message) use ($name): int {
return $total + (int) ($name === $message->name);
},
0,
);
}
}

View File

@ -12,6 +12,7 @@ use ReflectionException;
use ReflectionFunction;
use ReflectionNamedType;
use ReflectionParameter;
use ReflectionUnionType;
/**
* @internal
@ -60,6 +61,21 @@ final class Reflection
return Closure::fromCallable($callable)->bindTo(TestSuite::getInstance()->test)(...$args);
}
/**
* Bind a callable to the TestCase and return the result,
* passing in the current dataset values as arguments.
*
* @return mixed
*/
public static function bindCallableWithData(callable $callable)
{
$test = TestSuite::getInstance()->test;
return $test === null
? static::bindCallable($callable)
: Closure::fromCallable($callable)->bindTo($test)(...$test->getProvidedData());
}
/**
* Infers the file name from the given closure.
*/
@ -94,10 +110,6 @@ final class Reflection
}
}
if ($reflectionProperty === null) {
throw ShouldNotHappen::fromMessage('Reflection property not found.');
}
$reflectionProperty->setAccessible(true);
return $reflectionProperty->getValue($object);
@ -128,10 +140,6 @@ final class Reflection
}
}
if ($reflectionProperty === null) {
throw ShouldNotHappen::fromMessage('Reflection property not found.');
}
$reflectionProperty->setAccessible(true);
$reflectionProperty->setValue($object, $value);
}
@ -163,4 +171,37 @@ final class Reflection
return $name;
}
/**
* Receive a map of function argument names to their types.
*
* @return array<string, string>
*/
public static function getFunctionArguments(Closure $function): array
{
$parameters = (new ReflectionFunction($function))->getParameters();
$arguments = [];
foreach ($parameters as $parameter) {
/** @var ReflectionNamedType|ReflectionUnionType|null $types */
$types = ($parameter->hasType()) ? $parameter->getType() : null;
if (is_null($types)) {
$arguments[$parameter->getName()] = 'mixed';
continue;
}
$arguments[$parameter->getName()] = implode('|', array_map(
static function (ReflectionNamedType $type): string {
return $type->getName();
},
($types instanceof ReflectionNamedType)
? [$types] // NOTE: normalize as list of to handle unions
: $types->getTypes(),
));
}
return $arguments;
}
}

View File

@ -1,5 +1,5 @@
<?php
test('{name}', function () {
assertTrue(true);
expect(true)->toBeTrue();
});

View File

@ -96,11 +96,17 @@
✓ more than two datasets with (2) / (4) / (5)
✓ more than two datasets with (2) / (4) / (6)
✓ more than two datasets did the job right
✓ it can resolve a dataset after the test case is available with (Closure Object (...))
✓ it can resolve a dataset after the test case is available with shared yield sets with (Closure Object (...)) #1
✓ it can resolve a dataset after the test case is available with shared yield sets with (Closure Object (...)) #2
✓ it can resolve a dataset after the test case is available with shared array sets with (Closure Object (...)) #1
✓ it can resolve a dataset after the test case is available with shared array sets with (Closure Object (...)) #2
PASS Tests\Features\Exceptions
✓ it gives access the the underlying expectException
✓ it catch exceptions
✓ it catch exceptions and messages
✓ it can just define the message
PASS Tests\Features\Expect\HigherOrder\methods
✓ it can access methods
@ -112,11 +118,14 @@
✓ it works with sequence
✓ it can compose complex expectations
✓ it can handle nested method calls
✓ it works with higher order tests
PASS Tests\Features\Expect\HigherOrder\methodsAndProperties
✓ it can access methods and properties
✓ it can handle nested methods and properties
✓ it works with higher order tests
✓ it can start a new higher order expectation using the and syntax
✓ it can start a new higher order expectation using the and syntax in higher order tests
PASS Tests\Features\Expect\HigherOrder\properties
✓ it allows properties to be accessed from the value
@ -128,6 +137,7 @@
✓ it can compose complex expectations
✓ it works with objects
✓ it works with nested properties
✓ it works with higher order tests
PASS Tests\Features\Expect\each
✓ an exception is thrown if the the type is not iterable
@ -216,6 +226,11 @@
PASS Tests\Features\Expect\toBeGreatherThanOrEqual
✓ passes
✓ failures
✓ not failures
PASS Tests\Features\Expect\toBeIn
✓ passes
✓ failures
✓ not failures
PASS Tests\Features\Expect\toBeInfinite
@ -315,9 +330,16 @@
PASS Tests\Features\Expect\toContain
✓ passes strings
✓ passes strings with multiple needles
✓ passes arrays
✓ passes arrays with multiple needles
✓ passes with array needles
✓ failures
✓ failures with multiple needles (all failing)
✓ failures with multiple needles (some failing)
✓ not failures
✓ not failures with multiple needles (all failing)
✓ not failures with multiple needles (some failing)
PASS Tests\Features\Expect\toEndWith
✓ pass
@ -411,7 +433,12 @@
✓ it proxies calls to object
✓ it is capable doing multiple assertions
✓ it resolves expect callables correctly
✓ does not treat method names as callables
✓ it can tap into the test
✓ it can pass datasets into the expect callables with (1, 2, 3)
✓ it can pass datasets into the tap callable with (1, 2, 3)
✓ it can pass shared datasets into callables with (1)
✓ it can pass shared datasets into callables with (2)
WARN Tests\Features\Incompleted
… incompleted
@ -444,6 +471,8 @@
✓ it do not skips with falsy closure condition
- it skips with condition and message → skipped because foo
- it skips when skip after assertion
- it can use something in the test case as a condition → This test was skipped
- it can user higher order callables and skip
PASS Tests\Features\Test
✓ a test
@ -457,12 +486,14 @@
PASS Tests\Hooks\AfterAllTest
✓ global afterAll execution order
✓ it only gets called once per file
PASS Tests\Hooks\AfterEachTest
✓ global afterEach execution order
PASS Tests\Hooks\BeforeAllTest
✓ global beforeAll execution order
✓ it only gets called once per file
PASS Tests\Hooks\BeforeEachTest
✓ global beforeEach execution order
@ -554,10 +585,14 @@
PASS Tests\Unit\TestSuite
✓ it does not allow to add the same test description twice
✓ it alerts users about tests with arguments but no input
PASS Tests\Visual\Help
✓ visual snapshot of help command output
PASS Tests\Visual\JUnit
✓ it is can successfully call all public methods
PASS Tests\Visual\SingleTestOrDirectory
✓ allows to run a single test
✓ allows to run a directory
@ -567,6 +602,9 @@
WARN Tests\Visual\Success
- visual snapshot of test suite on success
PASS Tests\Visual\TeamCity
✓ it is can successfully call all public methods
PASS Tests\Features\Depends
✓ first
✓ second
@ -581,5 +619,5 @@
✓ it is a test
✓ it uses correct parent class
Tests: 4 incompleted, 7 skipped, 365 passed
Tests: 4 incompleted, 9 skipped, 395 passed

11
tests/Datasets/Bound.php Normal file
View File

@ -0,0 +1,11 @@
<?php
dataset('bound.closure', function () {
yield function () { return 1; };
yield function () { return 2; };
});
dataset('bound.array', [
function () { return 1; },
function () { return 2; },
]);

View File

@ -5,6 +5,10 @@ use Pest\Exceptions\DatasetAlreadyExist;
use Pest\Exceptions\DatasetDoesNotExist;
use Pest\Plugin;
beforeEach(function () {
$this->foo = 'bar';
});
it('throws exception if dataset does not exist', function () {
$this->expectException(DatasetDoesNotExist::class);
$this->expectExceptionMessage("A dataset with the name `first` does not exist. You can create it using `dataset('first', ['a', 'b']);`.");
@ -223,3 +227,17 @@ test('more than two datasets', function ($text_a, $text_b, $text_c) use ($state,
test('more than two datasets did the job right', function () use ($state) {
expect($state->text)->toBe('121212121212131423241314232411122122111221221112212213142324135136145146235236245246');
});
it('can resolve a dataset after the test case is available', function ($result) {
expect($result)->toBe('bar');
})->with([
function () { return $this->foo; },
]);
it('can resolve a dataset after the test case is available with shared yield sets', function ($result) {
expect($result)->toBeInt()->toBeLessThan(3);
})->with('bound.closure');
it('can resolve a dataset after the test case is available with shared array sets', function ($result) {
expect($result)->toBeInt()->toBeLessThan(3);
})->with('bound.array');

View File

@ -0,0 +1,16 @@
<?php
use PHPUnit\Framework\ExpectationFailedException;
test('passes', function () {
expect('a')->toBeIn(['a', 'b', 'c']);
expect('d')->not->toBeIn(['a', 'b', 'c']);
});
test('failures', function () {
expect('d')->toBeIn(['a', 'b', 'c']);
})->throws(ExpectationFailedException::class);
test('not failures', function () {
expect('a')->not->toBeIn(['a', 'b', 'c']);
})->throws(ExpectationFailedException::class);

View File

@ -3,17 +3,45 @@
use PHPUnit\Framework\ExpectationFailedException;
test('passes strings', function () {
expect([1, 2, 42])->toContain(42);
expect('Nuno')->toContain('Nu');
});
test('passes strings with multiple needles', function () {
expect('Nuno')->toContain('Nu', 'no');
});
test('passes arrays', function () {
expect('Nuno')->toContain('Nu');
expect([1, 2, 42])->toContain(42);
});
test('passes arrays with multiple needles', function () {
expect([1, 2, 42])->toContain(42, 2);
});
test('passes with array needles', function () {
expect([[1, 2, 3], 2, 42])->toContain(42, [1, 2, 3]);
});
test('failures', function () {
expect([1, 2, 42])->toContain(3);
})->throws(ExpectationFailedException::class);
test('failures with multiple needles (all failing)', function () {
expect([1, 2, 42])->toContain(3, 4);
})->throws(ExpectationFailedException::class);
test('failures with multiple needles (some failing)', function () {
expect([1, 2, 42])->toContain(1, 3, 4);
})->throws(ExpectationFailedException::class);
test('not failures', function () {
expect([1, 2, 42])->not->toContain(42);
})->throws(ExpectationFailedException::class);
test('not failures with multiple needles (all failing)', function () {
expect([1, 2, 42])->not->toContain(42, 2);
})->throws(ExpectationFailedException::class);
test('not failures with multiple needles (some failing)', function () {
expect([1, 2, 42])->not->toContain(42, 1);
})->throws(ExpectationFailedException::class);

View File

@ -27,4 +27,20 @@ it('can tap into the test')
->toBe('foo')
->and('hello world')->toBeString();
it('can pass datasets into the expect callables')
->with([[1, 2, 3]])
->expect(function (...$numbers) { return $numbers; })->toBe([1, 2, 3])
->and(function (...$numbers) { return $numbers; })->toBe([1, 2, 3]);
it('can pass datasets into the tap callable')
->with([[1, 2, 3]])
->tap(function (...$numbers) { expect($numbers)->toBe([1, 2, 3]); });
it('can pass shared datasets into callables')
->with('numbers.closure.wrapped')
->expect(function ($value) { return $value; })
->and(function ($value) { return $value; })
->tap(function ($value) { expect($value)->toBeInt(); })
->toBeInt();
afterEach()->assertTrue(true);

View File

@ -2,26 +2,50 @@
global $globalHook;
// NOTE: this test does not have a $globalHook->calls offset since it is first
// in the directory and thus will always run before the others. See also the
// BeforeAllTest.php for details.
uses()->afterAll(function () use ($globalHook) {
expect($globalHook)
->toHaveProperty('afterAll')
->and($globalHook->afterAll)
->toBe(0);
->toBe(0)
->and($globalHook->calls)
->afterAll
->toBe(1);
$globalHook->afterAll = 1;
$globalHook->calls->afterAll++;
});
afterAll(function () use ($globalHook) {
expect($globalHook)
->toHaveProperty('afterAll')
->and($globalHook->afterAll)
->toBe(1);
->toBe(1)
->and($globalHook->calls)
->afterAll
->toBe(2);
$globalHook->afterAll = 2;
$globalHook->calls->afterAll++;
});
test('global afterAll execution order', function () use ($globalHook) {
expect($globalHook)
->not()
->toHaveProperty('afterAll');
->toHaveProperty('afterAll')
->and($globalHook->calls)
->afterAll
->toBe(0);
});
it('only gets called once per file', function () use ($globalHook) {
expect($globalHook)
->not()
->toHaveProperty('afterAll')
->and($globalHook->calls)
->afterAll
->toBe(0);
});

View File

@ -1,28 +1,56 @@
<?php
use Pest\Support\Str;
global $globalHook;
uses()->beforeAll(function () use ($globalHook) {
// HACK: we have to determine our $globalHook->calls baseline. This is because
// two other tests are executed before this one due to filename ordering.
$args = $_SERVER['argv'] ?? [];
$single = isset($args[1]) && Str::endsWith(__FILE__, $args[1]);
$offset = $single ? 0 : 2;
uses()->beforeAll(function () use ($globalHook, $offset) {
expect($globalHook)
->toHaveProperty('beforeAll')
->and($globalHook->beforeAll)
->toBe(0);
->toBe(0)
->and($globalHook->calls)
->beforeAll
->toBe(1 + $offset);
$globalHook->beforeAll = 1;
$globalHook->calls->beforeAll++;
});
beforeAll(function () use ($globalHook) {
beforeAll(function () use ($globalHook, $offset) {
expect($globalHook)
->toHaveProperty('beforeAll')
->and($globalHook->beforeAll)
->toBe(1);
->toBe(1)
->and($globalHook->calls)
->beforeAll
->toBe(2 + $offset);
$globalHook->beforeAll = 2;
$globalHook->calls->beforeAll++;
});
test('global beforeAll execution order', function () use ($globalHook) {
test('global beforeAll execution order', function () use ($globalHook, $offset) {
expect($globalHook)
->toHaveProperty('beforeAll')
->and($globalHook->beforeAll)
->toBe(2);
->toBe(2)
->and($globalHook->calls)
->beforeAll
->toBe(3 + $offset);
});
it('only gets called once per file', function () use ($globalHook, $offset) {
expect($globalHook)
->beforeAll
->toBe(2)
->and($globalHook->calls)
->beforeAll
->toBe(3 + $offset);
});

View File

@ -2,7 +2,8 @@
uses()->group('integration')->in('Visual');
$globalHook = (object) []; // NOTE: global test value container to be mutated and checked across files, as needed
// NOTE: global test value container to be mutated and checked across files, as needed
$globalHook = (object) ['calls' => (object) ['beforeAll' => 0, 'afterAll' => 0]];
uses()
->beforeEach(function () {
@ -10,11 +11,13 @@ uses()
})
->beforeAll(function () use ($globalHook) {
$globalHook->beforeAll = 0;
$globalHook->calls->beforeAll++;
})
->afterEach(function () {
$this->ith = 0;
})
->afterAll(function () use ($globalHook) {
$globalHook->afterAll = 0;
$globalHook->calls->afterAll++;
})
->in('Hooks');

View File

@ -1,5 +1,6 @@
<?php
use Pest\Exceptions\DatasetMissing;
use Pest\Exceptions\TestAlreadyExist;
use Pest\TestSuite;
@ -7,7 +8,17 @@ it('does not allow to add the same test description twice', function () {
$testSuite = new TestSuite(getcwd(), 'tests');
$test = function () {};
$testSuite->tests->set(new \Pest\Factories\TestCaseFactory(__FILE__, 'foo', $test));
$this->expectException(TestAlreadyExist::class);
$this->expectExceptionMessage(sprintf('A test with the description `%s` already exist in the filename `%s`.', 'foo', __FILE__));
$testSuite->tests->set(new \Pest\Factories\TestCaseFactory(__FILE__, 'foo', $test));
});
})->throws(
TestAlreadyExist::class,
sprintf('A test with the description `%s` already exist in the filename `%s`.', 'foo', __FILE__),
);
it('alerts users about tests with arguments but no input', function () {
$testSuite = new TestSuite(getcwd(), 'tests');
$test = function (int $arg) {};
$testSuite->tests->set(new \Pest\Factories\TestCaseFactory(__FILE__, 'foo', $test));
})->throws(
DatasetMissing::class,
sprintf("A test with the description '%s' has %d argument(s) ([%s]) and no dataset(s) provided in %s", 'foo', 1, 'int $arg', __FILE__),
);

29
tests/Visual/JUnit.php Normal file
View File

@ -0,0 +1,29 @@
<?php
use Pest\Logging\JUnit;
use PHPUnit\Framework\AssertionFailedError;
use PHPUnit\Framework\TestSuite;
use PHPUnit\Framework\Warning;
beforeEach(function () {
file_put_contents(__DIR__ . '/junit.html', '');
});
it('is can successfully call all public methods', function () {
$junit = new JUnit(__DIR__ . '/junit.html');
$junit->startTestSuite(new TestSuite());
$junit->startTest($this);
$junit->addError($this, new Exception(), 0);
$junit->addFailure($this, new AssertionFailedError(), 0);
$junit->addWarning($this, new Warning(), 0);
$junit->addIncompleteTest($this, new Exception(), 0);
$junit->addRiskyTest($this, new Exception(), 0);
$junit->addSkippedTest($this, new Exception(), 0);
$junit->endTest($this, 0);
$junit->endTestSuite(new TestSuite());
$this->expectNotToPerformAssertions();
});
afterEach(function () {
unlink(__DIR__ . '/junit.html');
});

32
tests/Visual/TeamCity.php Normal file
View File

@ -0,0 +1,32 @@
<?php
use Pest\Logging\TeamCity;
use PHPUnit\Framework\AssertionFailedError;
use PHPUnit\Framework\TestResult;
use PHPUnit\Framework\TestSuite;
use PHPUnit\Framework\Warning;
use PHPUnit\TextUI\DefaultResultPrinter;
beforeEach(function () {
file_put_contents(__DIR__ . '/output.txt', '');
});
it('is can successfully call all public methods', function () {
$teamCity = new TeamCity(__DIR__ . '/output.txt', false, DefaultResultPrinter::COLOR_ALWAYS);
expect($teamCity::isPestTest($this))->toBeTrue();
$teamCity->startTestSuite(new TestSuite());
$teamCity->startTest($this);
$teamCity->addError($this, new Exception('Don\'t worry about this error. Its purposeful.'), 0);
$teamCity->addFailure($this, new AssertionFailedError('Don\'t worry about this error. Its purposeful.'), 0);
$teamCity->addWarning($this, new Warning(), 0);
$teamCity->addIncompleteTest($this, new Exception(), 0);
$teamCity->addRiskyTest($this, new Exception(), 0);
$teamCity->addSkippedTest($this, new Exception(), 0);
$teamCity->endTest($this, 0);
$teamCity->printResult(new TestResult());
$teamCity->endTestSuite(new TestSuite());
});
afterEach(function () {
unlink(__DIR__ . '/output.txt');
});