Skip to content
This repository has been archived by the owner on Aug 1, 2023. It is now read-only.

Commit

Permalink
[bc break] bring in some FB compatibility changes.
Browse files Browse the repository at this point in the history
Needed for FB projects using fbexpect.

These changes mostly improve type safety.

- `toContain()` and `toNotContain()` now only operate on
  traversables/containers
- added `toContainSubstring()` and `toNotContainSubstring()`
- `toThrow()` can now only be used on functions
- `toThrow()` now returns the exception
- removed `toThrowWhenCalledWithArgs()` - use a lambda with `toThrow()`
  instead
  • Loading branch information
fredemmott committed Oct 21, 2019
1 parent 641199f commit b01e21a
Show file tree
Hide file tree
Showing 2 changed files with 55 additions and 78 deletions.
90 changes: 43 additions & 47 deletions src/ExpectObj.hack
Original file line number Diff line number Diff line change
Expand Up @@ -227,30 +227,39 @@ class ExpectObj<T> extends Assert {
}

/**
* Assert: For strings, Str\contains($actual, $needle) !== false
* For containers (array, Map, Set, etc) or objects which implement
* Traversable, iterate through $actual and see if any element ==
* $needle.
* Assert: iterate through $actual and see if any element == $needle.
* Note: If $needle is an object, === will be used.
*/
public function toContain(
mixed $needle,
public function toContain<TVal>(
TVal $needle,
string $msg = '',
mixed ...$args
): void {
): void where T as Traversable<TVal>{
$msg = \vsprintf($msg, $args);
$this->assertContains($needle, not_hack_array($this->var), $msg);
}

/**
* Assert: $actual contains the substring $expected
*/
public function toContainSubstring(
string $expected,
string $msg = '',
mixed ...$args
): void where T = string {
$msg = \vsprintf($msg, $args);
$this->assertContains($expected, $this->var, $msg);
}

/**
* Assert: That the KeyedTraversible $key has a key set.
* Note: If $key is a Set, use assertContains.
*/
public function toContainKey(
mixed $key,
public function toContainKey<TKey as arraykey, TVal>(
TKey $key,
string $msg = '',
mixed ...$args
): void {
): void where T as KeyedContainer<TKey, TVal> {
$msg = \vsprintf($msg, $args);
$obj = $this->var;
invariant(
Expand Down Expand Up @@ -441,13 +450,19 @@ class ExpectObj<T> extends Assert {
}

/**
* Assert: For strings, Str\contains($actual, $needle) !== false
* For containers (array, Map, Set, etc) or objects which implement
* Traversable, iterate through $actual and make sure there is no
* Assert: iterate through $actual and make sure there is no
* element for which $element == $needle.
* Note: If $needle is an object, === will be used.
*/
public function toNotContain(mixed $expected, string $msg = '', mixed ...$args): void {
public function toNotContain<TVal>(TVal $expected, string $msg = '', mixed ...$args): void where T as Traversable<TVal> {
$msg = \vsprintf($msg, $args);
$this->assertNotContains($expected, not_hack_array($this->var), $msg);
}

/**
* Assert: $actual does not contain the substring $expected
*/
public function toNotContainSubstring(string $expected, string $msg = '', mixed ...$args): void where T = string {
$msg = \vsprintf($msg, $args);
$this->assertNotContains($expected, not_hack_array($this->var), $msg);
}
Expand All @@ -456,7 +471,7 @@ class ExpectObj<T> extends Assert {
* Assert: That the KeyedTraversible $key has a key set.
* Note: If $key is a Set, use assertContains.
*/
public function toNotContainKey(arraykey $key, string $msg = '', mixed ...$args): void where T as KeyedContainer<arraykey, mixed> {
public function toNotContainKey<TKey as arraykey, TVal>(TKey $key, string $msg = '', mixed ...$args): void where T as KeyedContainer<TKey, TVal> {
$msg = \vsprintf($msg, $args);
$obj = $this->var;
$this->assertFalse(\array_key_exists($key, $obj), $msg);
Expand Down Expand Up @@ -524,59 +539,40 @@ class ExpectObj<T> extends Assert {
*
* expect(function() { invariant_violation('fail'); })
* ->toThrow(InvariantViolationException::class, 'fail');
*
* If `TRet` is an `Awaitable<_>` (e.g. if an async function is provided),
* the awaitable will be awaited.
*/
public function toThrow<Tclass as \Exception>(
classname<Tclass> $exception_class,
public function toThrow<TException as \Throwable, TRet>(
classname<TException> $exception_class,
?string $expected_exception_message = null,
?string $msg = null,
mixed ...$args
): void {
$msg = \vsprintf($msg, $args);
$this->toThrowWhenCalledWith(
varray[],
$exception_class,
$expected_exception_message,
$msg,
);
}

/**
* Asserts: Function throws exception of given type and with the given
* exception message (optional)
*
* Example usage:
*
* expect(
* function($a) { if ($a == 'foo') { invariant_violation('fail'); }}
* )->toThrowWhenCalledWith(array('foo'), 'InvariantViolationException');
*/
public function toThrowWhenCalledWith<Tclass as \Exception>(
Container<mixed> $args,
classname<Tclass> $exception_class,
?string $expected_exception_message = null,
?string $desc = null,
): void {
$exception = $this->tryCallWithArgsReturnException($args, $exception_class);
): TException where T = (function(): TRet) {
$msg = \vsprintf($msg ?? '', $args);
$exception = $this->tryCallWithArgsReturnException(vec[], $exception_class);

if (!$exception) {
throw new \Exception(
$desc.": Expected exception ".$exception_class." wasn't thrown",
$msg.": Expected exception ".$exception_class." wasn't thrown",
);
}

if ($expected_exception_message !== null) {
$message = $exception->getMessage();

$this->assertContains($expected_exception_message, $message, $desc ?? '');
$this->assertContains($expected_exception_message, $message, $msg);
}

return $exception;
}

/***************************************
***************************************
**** Private implementation details ***
***************************************
***************************************/
private function tryCallWithArgsReturnException<Tclass as \Exception>(
private function tryCallWithArgsReturnException<Tclass as \Throwable>(
Container<mixed> $args,
classname<Tclass> $expected_exception_type,
): ?Tclass {
Expand Down
43 changes: 12 additions & 31 deletions tests/ExpectObjTest.hack
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ final class ExpectObjTest extends HackTest {
expect(dict[])->toBeType('KeyedContainer');
expect(varray[1, 2, 3])->toContain(2);
expect(varray[1, 2, 3])->toNotContain(7);
expect('foo')->toContainSubstring('foo');
expect('foo')->toContainSubstring('o');
expect('foo')->toNotContainSubstring('a');
expect(1)->toAlmostEqual(1);
expect(null)->toAlmostEqual(null);

Expand Down Expand Up @@ -241,31 +244,6 @@ final class ExpectObjTest extends HackTest {
//
// Tests for toThrow methods
//
public function testToThrowWhenCalledWithSuccess(): void {
expect(
function() {
throw new \Exception();
},
)->toThrow(\Exception::class);

expect(
(classname<\Exception> $class) ==> {
if ($class === ExpectObjTestException::class) {
throw new ExpectObjTestException();
}
},
)->toThrowWhenCalledWith(
varray[ExpectObjTestException::class],
ExpectObjTestException::class,
);

expect(
function() {
throw new ExpectObjTestException('test msg');
},
)->toThrow(ExpectObjTestException::class, 'test msg');
}

public function testToThrowWithMessage(): void {
expect(
function() {
Expand All @@ -291,16 +269,14 @@ final class ExpectObjTest extends HackTest {

public function testAwaitableFunctionGetsPrepped(): void {
expect(
async (mixed $class) ==> {
async () ==> {
$class = ExpectObjTestException::class;
await self::stopEagerExecution();
if ($class === ExpectObjTestException::class) {
throw new ExpectObjTestException();
}
},
)->toThrowWhenCalledWith(
varray[ExpectObjTestException::class],
ExpectObjTestException::class,
);
)->toThrow(ExpectObjTestException::class);
}

public function testToHaveSameShapeAsSuccess(): void {
Expand Down Expand Up @@ -466,9 +442,14 @@ final class ExpectObjTest extends HackTest {
try {
expect("a\nb\nd\n")->toBeSame("a\nb\nc\n");
} catch (ExpectationFailedException $e) {
expect($e->getMessage())->toContain(" a\n b\n-c\n+d\n");
expect($e->getMessage())->toContainSubstring(" a\n b\n-c\n+d\n");
return;
}
self::fail("Should have thrown an exception");
}

public function testToThrowReturnsException(): void {
$e = expect(() ==> { throw new \Exception("Hello, world"); })->toThrow(\Exception::class);
expect($e->getMessage())->toContainSubstring('Hello, world');
}
}

0 comments on commit b01e21a

Please sign in to comment.