Skip to content
This repository has been archived by the owner on Jun 16, 2021. It is now read-only.

Add support to dot notation in toHaveKey expectation #15

Closed
wants to merge 10 commits into from
12 changes: 10 additions & 2 deletions src/Expectation.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@

use BadMethodCallException;
use Pest\Expectations\Concerns\Extendable;
use Pest\Expectations\Helpers\Arr;
use PHPUnit\Framework\Assert;
use PHPUnit\Framework\Constraint\Constraint;
use PHPUnit\Framework\ExpectationFailedException;
use SebastianBergmann\Exporter\Exporter;

/**
Expand Down Expand Up @@ -522,10 +524,16 @@ public function toHaveKey($key, $value = null): Expectation
$array = (array) $this->value;
}

Assert::assertArrayHasKey($key, $array);
try {
Assert::assertTrue(Arr::has($array, $key));

/* @phpstan-ignore-next-line */
} catch (ExpectationFailedException $exception) {
throw new ExpectationFailedException("Failed asserting that an array has the key '$key'", $exception->getComparisonFailure());
}

if (func_num_args() > 1) {
Assert::assertEquals($value, $array[$key]);
Assert::assertEquals($value, Arr::get($array, $key));
}

return $this;
Expand Down
68 changes: 68 additions & 0 deletions src/Helpers/Arr.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<?php

declare(strict_types=1);

namespace Pest\Expectations\Helpers;

/**
* Credits: most of this class methods and implementations
* belongs to the Arr helper of laravel/framework project
* (https://github.com/laravel/framework).
*
* @internal
*/
final class Arr
fabio-ivona marked this conversation as resolved.
Show resolved Hide resolved
{
/**
* @param array<mixed> $array
* @param string|int $key
*/
public static function has(array $array, $key): bool
{
$key = (string) $key;

if (array_key_exists($key, $array)) {
return true;
}

foreach (explode('.', $key) as $segment) {
if (is_array($array) && array_key_exists($segment, $array)) {
$array = $array[$segment];
} else {
return false;
}
}

return true;
}

/**
* @param array<mixed> $array
* @param string|int $key
* @param null $default
*
* @return array|mixed|null
*/
public static function get(array $array, $key, $default = null)
{
$key = (string) $key;

if (array_key_exists($key, $array)) {
return $array[$key];
}

if (strpos($key, '.') === false) {
return $array[$key] ?? $default;
}

foreach (explode('.', $key) as $segment) {
if (is_array($array) && array_key_exists($segment, $array)) {
$array = $array[$segment];
} else {
return $default;
}
}

return $array;
}
}
68 changes: 61 additions & 7 deletions tests/Expect/toHaveKey.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,68 @@

use PHPUnit\Framework\ExpectationFailedException;

test('pass', function () {
expect(['a' => 1, 'b', 'c' => 'world'])->toHaveKey('c');
});
$test_array = [
'a' => 1,
'b',
'c' => 'world',
'd' => [
'e' => 'hello',
],
'key.with.dots' => false,
];

test('failures', function () {
expect(['a' => 1, 'b', 'c' => 'world'])->toHaveKey('hello');
test('pass')->expect($test_array)->toHaveKey('c');
test('pass with nested key')->expect($test_array)->toHaveKey('d.e');
test('pass with plain key with dots')->expect($test_array)->toHaveKey('key.with.dots');

test('pass with value check')->expect($test_array)->toHaveKey('c', 'world');
test('pass with value check and nested key')->expect($test_array)->toHaveKey('d.e', 'hello');
test('pass with value check and plain key with dots')->expect($test_array)->toHaveKey('key.with.dots', false);

test('failures', function () use ($test_array) {
expect($test_array)->toHaveKey('foo');
})->throws(ExpectationFailedException::class, "Failed asserting that an array has the key 'foo'");

test('failures with nested key', function () use ($test_array) {
expect($test_array)->toHaveKey('d.bar');
})->throws(ExpectationFailedException::class, "Failed asserting that an array has the key 'd.bar'");

test('failures with plain key with dots', function () use ($test_array) {
expect($test_array)->toHaveKey('missing.key.with.dots');
})->throws(ExpectationFailedException::class, "Failed asserting that an array has the key 'missing.key.with.dots'");

test('fails with wrong value', function () use ($test_array) {
expect($test_array)->toHaveKey('c', 'bar');
})->throws(ExpectationFailedException::class);

test('fails with wrong value and nested key', function () use ($test_array) {
expect($test_array)->toHaveKey('d.e', 'foo');
})->throws(ExpectationFailedException::class);

test('fails with wrong value and plain key with dots', function () use ($test_array) {
expect($test_array)->toHaveKey('key.with.dots', true);
})->throws(ExpectationFailedException::class);

test('not failures', function () use ($test_array) {
expect($test_array)->not->toHaveKey('c');
})->throws(ExpectationFailedException::class, "Expecting Array (...) not to have key 'c'");

test('not failures with nested key', function () use ($test_array) {
expect($test_array)->not->toHaveKey('d.e');
})->throws(ExpectationFailedException::class, "Expecting Array (...) not to have key 'd.e'");

test('not failures with plain key with dots', function () use ($test_array) {
expect($test_array)->not->toHaveKey('key.with.dots');
})->throws(ExpectationFailedException::class, "Expecting Array (...) not to have key 'key.with.dots'");

test('not failures with correct value', function () use ($test_array) {
expect($test_array)->not->toHaveKey('c', 'world');
})->throws(ExpectationFailedException::class);

test('not failures with correct value and with nested key', function () use ($test_array) {
expect($test_array)->not->toHaveKey('d.e', 'hello');
})->throws(ExpectationFailedException::class);

test('not failures', function () {
expect(['a' => 1, 'hello' => 'world', 'c'])->not->toHaveKey('hello');
test('not failures with correct value and with plain key with dots', function () use ($test_array) {
expect($test_array)->not->toHaveKey('key.with.dots', false);
})->throws(ExpectationFailedException::class);
6 changes: 3 additions & 3 deletions tests/Expect/toHaveKeys.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@
use PHPUnit\Framework\ExpectationFailedException;

test('pass', function () {
expect(['a' => 1, 'b', 'c' => 'world'])->toHaveKeys(['a', 'c']);
expect(['a' => 1, 'b', 'c' => 'world', 'foo' => ['bar' => 'baz']])->toHaveKeys(['a', 'c', 'foo.bar']);
});

test('failures', function () {
expect(['a' => 1, 'b', 'c' => 'world'])->toHaveKeys(['a', 'd']);
expect(['a' => 1, 'b', 'c' => 'world', 'foo' => ['bar' => 'baz']])->toHaveKeys(['a', 'd', 'foo.bar', 'hello.world']);
})->throws(ExpectationFailedException::class);

test('not failures', function () {
expect(['a' => 1, 'hello' => 'world', 'c'])->not->toHaveKeys(['hello', 'c']);
expect(['a' => 1, 'b', 'c' => 'world', 'foo' => ['bar' => 'baz']])->not->toHaveKeys(['foo.bar', 'c', 'z']);
})->throws(ExpectationFailedException::class);