diff --git a/src/ExpectObj.hack b/src/ExpectObj.hack index dd29b3f..4d9f743 100644 --- a/src/ExpectObj.hack +++ b/src/ExpectObj.hack @@ -227,30 +227,39 @@ class ExpectObj 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 $needle, string $msg = '', mixed ...$args - ): void { + ): void where T as Traversable{ $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 $key, string $msg = '', mixed ...$args - ): void { + ): void where T as KeyedContainer { $msg = \vsprintf($msg, $args); $obj = $this->var; invariant( @@ -441,13 +450,19 @@ class ExpectObj 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 $expected, string $msg = '', mixed ...$args): void where T as Traversable { + $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); } @@ -456,7 +471,7 @@ class ExpectObj 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 { + public function toNotContainKey(TKey $key, string $msg = '', mixed ...$args): void where T as KeyedContainer { $msg = \vsprintf($msg, $args); $obj = $this->var; $this->assertFalse(\array_key_exists($key, $obj), $msg); @@ -524,51 +539,32 @@ class ExpectObj 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( - classname $exception_class, + public function toThrow( + classname $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( - Container $args, - classname $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; } /*************************************** @@ -576,7 +572,7 @@ class ExpectObj extends Assert { **** Private implementation details *** *************************************** ***************************************/ - private function tryCallWithArgsReturnException( + private function tryCallWithArgsReturnException( Container $args, classname $expected_exception_type, ): ?Tclass { diff --git a/tests/ExpectObjTest.hack b/tests/ExpectObjTest.hack index 1c8fc9b..d7fbb51 100644 --- a/tests/ExpectObjTest.hack +++ b/tests/ExpectObjTest.hack @@ -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); @@ -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() { @@ -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 { @@ -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'); + } }