Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Code quality, fix bug, added chained methods, added new BDD methods. #51

Merged
merged 3 commits into from Aug 27, 2020
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
@@ -1,3 +1,4 @@
vendor
.idea
.phpunit.result.cache
composer.phar
4 changes: 4 additions & 0 deletions composer.json
Expand Up @@ -7,6 +7,10 @@
{
"name": "Michael Bodnarchuk",
"email": "davert@codeception.com"
},
{
"name": "Gustavo Nieves",
"homepage": "https://medium.com/@ganieves"
}
],
"require": {
Expand Down
232 changes: 39 additions & 193 deletions src/Codeception/Specify.php
@@ -1,224 +1,70 @@
<?php
<?php declare(strict_types=1);

namespace Codeception;

use Codeception\Specify\SpecifyTest;
use Codeception\Specify\ObjectProperty;
use PHPUnit\Framework\AssertionFailedError;
use Closure;
use Codeception\Specify\SpecifyBoostrap;
use PHPUnit\Framework\TestCase;

trait Specify
{
private $beforeSpecify = array();
private $afterSpecify = array();

/**
* @var \DeepCopy\DeepCopy()
*/
private $copier;

/**
* @var SpecifyTest
*/
private $currentSpecifyTest;

private $specifyName = '';

/**
* @return SpecifyTest
*/
public function getCurrentSpecifyTest()
{
return $this->currentSpecifyTest;
}

public function should($specification, \Closure $callable = null, $params = [])
{
$this->specify("should " . $specification, $callable, $params);
}

public function it($specification, \Closure $callable = null, $params = [])
{
$this->specify($specification, $callable, $params);
}

public function describe($specification, \Closure $callable = null)
{
$this->specify($specification, $callable);
}

public function specify($specification, \Closure $callable = null, $params = [])
{
if (!$callable) {
return;
}

/** @var $this TestCase **/
if (!$this->copier) {
$this->copier = new \DeepCopy\DeepCopy();
$this->copier->skipUncloneable();
}

$properties = $this->getSpecifyObjectProperties();

// prepare for execution
$examples = $this->getSpecifyExamples($params);
$showExamplesIndex = $examples !== [[]];

$specifyName = $this->specifyName;
$this->specifyName .= ' ' . $specification;

foreach ($examples as $idx => $example) {
$test = new SpecifyTest($callable->bindTo($this));
$this->currentSpecifyTest = $test;
$test->setName($this->getName() . ' |' . $this->specifyName);
$test->setExample($example);
if ($showExamplesIndex) {
$test->setName($this->getName() . ' |' . $this->specifyName . ' # example ' . $idx);
}

// copy current object properties
$this->specifyCloneProperties($properties);

if (!empty($this->beforeSpecify) && is_array($this->beforeSpecify)) {
foreach ($this->beforeSpecify as $closure) {
if ($closure instanceof \Closure) $closure->__invoke();
}
}

$test->run($this->getTestResultObject());
$this->specifyCheckMockObjects();

// restore object properties
$this->specifyRestoreProperties($properties);

if (!empty($this->afterSpecify) && is_array($this->afterSpecify)) {
foreach ($this->afterSpecify as $closure) {
if ($closure instanceof \Closure) $closure->__invoke();
}
}
}

// revert specify name
$this->specifyName = $specifyName;
}

/**
* @param $params
* @return array
* @throws \RuntimeException
*/
private function getSpecifyExamples($params)
{
if (isset($params['examples'])) {
if (!is_array($params['examples'])) throw new \RuntimeException("Examples should be an array");
return $params['examples'];
}
return [[]];
use SpecifyBoostrap {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bootstrap maybe?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about this naming:

SpecifyHooks

Before and After are hooks, we can name it like this

afterSpecify as public;
beforeSpecify as public;
cleanSpecify as public;
getCurrentSpecifyTest as public;
}

/**
* @return \ReflectionClass|null
*/
private function specifyGetPhpUnitReflection()
public function specify(string $thing, Closure $code = null, $examples = []): ?self
{
if ($this instanceof \PHPUnit\Framework\TestCase) {
return new \ReflectionClass(\PHPUnit\Framework\TestCase::class);
if ($code instanceof Closure) {
$this->runSpec($thing, $code, $examples);
return null;
}
return $this;
}

private function specifyCheckMockObjects()
public function describe(string $feature, Closure $code = null): ?self
{
if (($phpUnitReflection = $this->specifyGetPhpUnitReflection()) !== null) {
$verifyMockObjects = $phpUnitReflection->getMethod('verifyMockObjects');
$verifyMockObjects->setAccessible(true);
$verifyMockObjects->invoke($this);
if ($code instanceof Closure) {
$this->runSpec($feature, $code);
return null;
}
return $this;
}

function beforeSpecify(\Closure $callable = null)
{
$this->beforeSpecify[] = $callable->bindTo($this);
}

function afterSpecify(\Closure $callable = null)
{
$this->afterSpecify[] = $callable->bindTo($this);
}

function cleanSpecify()
public function it(string $specification, Closure $code = null, $examples = []): self
{
$this->beforeSpecify = $this->afterSpecify = array();
}

/**
* @param ObjectProperty[] $properties
*/
private function specifyRestoreProperties($properties)
{
foreach ($properties as $property) {
$property->restoreValue();
if ($code instanceof Closure) {
$this->runSpec($specification, $code, $examples);
return $this;
}
TestCase::markTestIncomplete();
return $this;
}

/**
* @return ObjectProperty[]
*/
private function getSpecifyObjectProperties()
public function its(string $specification, Closure $code = null, $examples = []): self
{
$objectReflection = new \ReflectionObject($this);
$properties = $objectReflection->getProperties();

if (($classProperties = $this->specifyGetClassPrivateProperties()) !== []) {
$properties = array_merge($properties, $classProperties);
}

$clonedProperties = [];

foreach ($properties as $property) {
/** @var $property \ReflectionProperty **/
$docBlock = $property->getDocComment();
if (!$docBlock) {
continue;
}
if (preg_match('~\*(\s+)?@specify\s?~', $docBlock)) {
$property->setAccessible(true);
$clonedProperties[] = new ObjectProperty($this, $property);
}
}

// isolate mockObjects property from PHPUnit\Framework\TestCase
if ($classReflection = $this->specifyGetPhpUnitReflection()) {
$property = $classReflection->getProperty('mockObjects');
// remove all mock objects inherited from parent scope(s)
$clonedProperties[] = new ObjectProperty($this, $property);
$property->setValue($this, []);
}

return $clonedProperties;
return $this->it($specification, $code, $examples);
}

private function specifyGetClassPrivateProperties()
public function should(string $behavior, Closure $code = null, $examples = []): self
{
static $properties = [];

if (!isset($properties[__CLASS__])) {
$reflection = new \ReflectionClass(__CLASS__);

$properties[__CLASS__] = (get_class($this) !== __CLASS__)
? $reflection->getProperties(\ReflectionProperty::IS_PRIVATE) : [];
if ($code instanceof Closure) {
$this->runSpec('should ' . $behavior, $code, $examples);
return $this;
}

return $properties[__CLASS__];
TestCase::markTestIncomplete();
return $this;
}

/**
* @param ObjectProperty[] $properties
*/
private function specifyCloneProperties($properties)
public function shouldNot(string $behavior, Closure $code = null, $examples = []): self
{
foreach ($properties as $property) {
$propertyValue = $property->getValue();
$property->setValue($this->copier->copy($propertyValue));
if ($code instanceof Closure) {
$this->runSpec('should not ' . $behavior, $code, $examples);
return $this;
}
TestCase::markTestIncomplete();
return $this;
}
}
9 changes: 6 additions & 3 deletions src/Codeception/Specify/ObjectProperty.php
@@ -1,6 +1,9 @@
<?php

namespace Codeception\Specify;

use ReflectionProperty;

/**
* Helper for manipulating by an object property.
*
Expand All @@ -14,7 +17,7 @@ class ObjectProperty
private $owner;

/**
* @var \ReflectionProperty|string
* @var ReflectionProperty|string
*/
private $property;

Expand All @@ -35,8 +38,8 @@ public function __construct($owner, $property, $value = null)
$this->owner = $owner;
$this->property = $property;

if (!($this->property instanceof \ReflectionProperty)) {
$this->property = new \ReflectionProperty($owner, $this->property);
if (!($this->property instanceof ReflectionProperty)) {
$this->property = new ReflectionProperty($owner, $this->property);
}

$this->property->setAccessible(true);
Expand Down