From 0a49ae3f4a6470e8cf2aa47efbc7713eb7560f04 Mon Sep 17 00:00:00 2001 From: Andrew Coulton Date: Thu, 29 Dec 2011 22:56:09 +0000 Subject: [PATCH 01/11] Only require vfsStream for running Mimic's own unit tests --- tests/mimic/request/store/PlaybackTest.php | 18 ++++++++++++------ tests/mimic/request/store/RecordingTest.php | 12 ++++++------ tests/mimic/response/FormatterBaseTest.php | 19 +++++++++++++------ 3 files changed, 31 insertions(+), 18 deletions(-) diff --git a/tests/mimic/request/store/PlaybackTest.php b/tests/mimic/request/store/PlaybackTest.php index 127054a..e8b1517 100644 --- a/tests/mimic/request/store/PlaybackTest.php +++ b/tests/mimic/request/store/PlaybackTest.php @@ -1,11 +1,5 @@ load() to return our test configs diff --git a/tests/mimic/response/FormatterBaseTest.php b/tests/mimic/response/FormatterBaseTest.php index f99de43..007b345 100644 --- a/tests/mimic/response/FormatterBaseTest.php +++ b/tests/mimic/response/FormatterBaseTest.php @@ -1,11 +1,5 @@ Date: Fri, 30 Dec 2011 04:25:39 +0000 Subject: [PATCH 02/11] Mimic_Unittest_Testcase provides useful assertions and setup for testing with Mimic --- classes/mimic/unittest/testcase.php | 103 ++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 classes/mimic/unittest/testcase.php diff --git a/classes/mimic/unittest/testcase.php b/classes/mimic/unittest/testcase.php new file mode 100644 index 0000000..ec31110 --- /dev/null +++ b/classes/mimic/unittest/testcase.php @@ -0,0 +1,103 @@ +mimic = Mimic::instance(); + $this->mimic->reset_requests(); + if ($this->_mimic_default_scenario) + { + $this->mimic->load_scenario($this->_mimic_default_scenario); + } + } + + /** + * Asserts that an expected number of requests were made + * @param integer $expected + */ + public function assertMimicRequestCount($expected) + { + $this->assertEquals($expected, $this->mimic->request_count()); + } + + /** + * Asserts that the URL of the most recent request is equal to an expected value + * @param string $expected + */ + public function assertMimicLastRequestURL($expected) + { + $this->assertEquals($expected, $this->mimic->last_request()->uri()); + } + + /** + * Asserts that the HTTP request method of the most recent request is equal to + * an expected value + * + * @param string $expected + */ + public function assertMimicLastRequestMethod($expected) + { + $this->assertEquals($expected, $this->mimic->last_request()->method()); + } + + /** + * Asserts that the most recent request included a header with the given value + * + * @param string $header The header name + * @param string $expected The expected value + */ + public function assertMimicLastRequestHeader($header, $expected) + { + $this->assertEquals($expected, $this->mimic->last_request()->headers($header)); + } + + /** + * Asserts that the most recent request included an expected $_GET parameter + * + * @param string $key + * @param string $expected + */ + public function assertMimicLastRequestQuery($key, $expected) + { + $this->assertEquals($expected, $this->mimic->last_request()->query($key)); + } + + /** + * Asserts that the request body of the most recent request was as expected + * @param string $expected + */ + public function assertMimicLastRequestBody($expected) + { + $this->assertEquals($expected, $this->mimic->last_request()->body()); + } + +} \ No newline at end of file From ada2f6ebd49d5afba5cdabb7da1e96c9ecb53fda Mon Sep 17 00:00:00 2001 From: Andrew Coulton Date: Fri, 30 Dec 2011 13:48:57 +0000 Subject: [PATCH 03/11] Explicitly set Request content-length header if not present - fixes #5 --- classes/request/client/mimic.php | 8 ++++++- tests/mimic/ClientTest.php | 39 +++++++++++++++++++++++++++++++- 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/classes/request/client/mimic.php b/classes/request/client/mimic.php index f1c50a8..2a23676 100644 --- a/classes/request/client/mimic.php +++ b/classes/request/client/mimic.php @@ -33,6 +33,12 @@ class Request_Client_Mimic extends Request_Client_External */ public function _send_message(Request $request) { + // Set a content-length if not set already (fixes #5) + if ($request->headers('content-length') === NULL) + { + $request->headers('content-length', (string) strlen($request->body())); + } + // Create OR retrieve the Mimic and Mimic_Request_Store instances $mimic = $this->mimic(); $store = $this->store(); @@ -145,4 +151,4 @@ protected function _send_external($request) return $client->_send_message($request); } -} // End Request_Client_Mimic \ No newline at end of file +} // End Request_Client_Mimic diff --git a/tests/mimic/ClientTest.php b/tests/mimic/ClientTest.php index 844032f..a6e6ea1 100644 --- a/tests/mimic/ClientTest.php +++ b/tests/mimic/ClientTest.php @@ -243,6 +243,43 @@ public function test_should_record_and_return_external_request_if_updating_enabl $returned_response = $this->_client->_send_message($request); $this->assertSame($response, $returned_response, "The same response instance was returned"); } + + public function provider_should_assign_request_content_length_if_missing() + { + return array( + array(NULL, '5', '5'), + array('foo', NULL, '3'), + array(NULL, NULL, '0'), + array('foo', '0', '0') + ); + } + + /** + * @group ticket.5 + * @dataProvider provider_should_assign_request_content_length_if_missing + */ + public function test_should_assign_request_content_length_if_missing($body, $set_length, $expect_length) + { + // Create a request + $request = Request::factory('http://foo.bar.com/a/page'); + $request->body($body); + if ($set_length !== NULL) + { + $request->headers('content-length', $set_length); + } + $response = $request->create_response(); + + // Set up Mimic expectations + $this->_mock_mimic_methods($this->once(), 'enable_updating', NULL, FALSE); + + // Mock a store to return response + $this->_mock_store_methods($this->once(), 'load', $request, $response); + + // Execute and check the content-length header is present + $this->_client->_send_message($request); + $this->assertSame($expect_length, $request->headers('content-length')); + + } } @@ -258,4 +295,4 @@ public function _send_message(Request $request) return $request->response(); } -} \ No newline at end of file +} From bd49aa029a61e9ab1ddc4abf72e02afbac27c4a1 Mon Sep 17 00:00:00 2001 From: Andrew Coulton Date: Fri, 30 Dec 2011 13:51:19 +0000 Subject: [PATCH 04/11] Fix link to Travis in README.md [ci skip] --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bc23518..600b8f1 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Mimic -**[View builds]((http://travis-ci.org/acoulton/mimic) | master: ![Build status - master branch](https://secure.travis-ci.org/acoulton/mimic.png?branch=master) | +**[View builds](http://travis-ci.org/acoulton/mimic) | master: ![Build status - master branch](https://secure.travis-ci.org/acoulton/mimic.png?branch=master) | develop: ![Build Status - develop branch](https://secure.travis-ci.org/acoulton/mimic.png?branch=develop) | overall: ![Build Status - overall](https://secure.travis-ci.org/acoulton/mimic.png)** From 364a80aeb51e83e2868eab8ee0e8d285df7848bf Mon Sep 17 00:00:00 2001 From: Andrew Coulton Date: Fri, 30 Dec 2011 14:10:46 +0000 Subject: [PATCH 05/11] Tests for Mimic_Unittest_Testcase setup method - continuing #4 --- tests/mimic/TestCaseTest.php | 73 ++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 tests/mimic/TestCaseTest.php diff --git a/tests/mimic/TestCaseTest.php b/tests/mimic/TestCaseTest.php new file mode 100644 index 0000000..902fa37 --- /dev/null +++ b/tests/mimic/TestCaseTest.php @@ -0,0 +1,73 @@ +setUp(); + + $this->assertSame($mimic, $testcase->mimic); + return $testcase; + } + + /** + * @depends test_setup_should_assign_mimic_instance_to_class + */ + public function test_setup_should_reset_requests(Mimic_TestCase_Foo $testcase) + { + // Pretend a request happened + $request = Request::factory('http://foo.bar.com/ok'); + $testcase->mimic->log_request($request); + $this->assertEquals(1, $testcase->mimic->request_count(), "Verify expectation pre-test"); + + // Setup + $testcase->setUp(); + + $this->assertEquals(0, $testcase->mimic->request_count()); + } + + public function provider_setup_should_set_scenario_if_set_in_class() + { + return array( + array(NULL, 'default'), + array('foo', 'foo') + ); + } + + /** + * @dataProvider provider_setup_should_set_scenario_if_set_in_class + */ + public function test_setup_should_set_scenario_if_set_in_class($property, $expected) + { + $testcase = new Mimic_TestCase_Foo; + $testcase->_mimic_default_scenario = $property; + $mimic = Mimic::instance(array(), TRUE); + $mimic->load_scenario('default'); + + $testcase->setUp(); + + $this->assertEquals($expected, $mimic->get_active_scenario()); + } +} + +/** + * Extension as the base class is abstract + */ +class Mimic_TestCase_Foo extends Mimic_Unittest_Testcase +{ + public $_mimic_default_scenario = NULL; +} From 2c7cf5817f74904d7e1b669ec0d6552bc1e37731 Mon Sep 17 00:00:00 2001 From: Andrew Coulton Date: Mon, 2 Jan 2012 14:05:49 +0000 Subject: [PATCH 06/11] Tests for first batch of test case assertions - continuing #4 --- tests/mimic/TestCaseTest.php | 233 ++++++++++++++++++++++++++++++++++- 1 file changed, 227 insertions(+), 6 deletions(-) diff --git a/tests/mimic/TestCaseTest.php b/tests/mimic/TestCaseTest.php index 902fa37..9e40b46 100644 --- a/tests/mimic/TestCaseTest.php +++ b/tests/mimic/TestCaseTest.php @@ -2,7 +2,7 @@ /** * Tests for the Mimic_Unittest_Testcase base testing class - * + * * @group mimic * @group mimic.testcase * @@ -19,7 +19,7 @@ public function test_setup_should_assign_mimic_instance_to_class() $testcase = new Mimic_TestCase_Foo; $mimic = Mimic::instance(array(), TRUE); $testcase->setUp(); - + $this->assertSame($mimic, $testcase->mimic); return $testcase; } @@ -33,10 +33,10 @@ public function test_setup_should_reset_requests(Mimic_TestCase_Foo $testcase) $request = Request::factory('http://foo.bar.com/ok'); $testcase->mimic->log_request($request); $this->assertEquals(1, $testcase->mimic->request_count(), "Verify expectation pre-test"); - + // Setup $testcase->setUp(); - + $this->assertEquals(0, $testcase->mimic->request_count()); } @@ -57,11 +57,232 @@ public function test_setup_should_set_scenario_if_set_in_class($property, $expec $testcase->_mimic_default_scenario = $property; $mimic = Mimic::instance(array(), TRUE); $mimic->load_scenario('default'); - + $testcase->setUp(); - + $this->assertEquals($expected, $mimic->get_active_scenario()); } + + /** + * Helper method to test that an assertion produces expected pass/fail result + * + * @param Mimic_Testcase $testcase The testcase instance + * @param string $method The method to call + * @param array $args Arguments to pass to the method + * @param boolean $should_pass Whether or not the assertion should pass + * @return void + */ + protected function _test_assertion($testcase, $method, $args, $should_pass) + { + try + { + // Call the assertion method + call_user_func_array(array($testcase, $method), $args); + } + catch (PHPUnit_Framework_ExpectationFailedException $e) + { + // Check that this is expected to fail + if ($should_pass) + { + $this->fail('Unexpected assertion failure with message '.$e->getMessage()); + } + return; + } + + // Check that it is expected to pass + if ( ! $should_pass) + { + $this->fail("Unexpected assertion pass for method $method"); + } + } + + /** + * Gets a testcase with a mock mimic attached ready for use in testing + * @param string $method + * @param mixed $return + * @return Mimic_TestCase_Foo + */ + protected function _testcase_with_mock_mimic($method, $return) + { + $testcase = new Mimic_TestCase_Foo; + + // Mock a Mimic to return the matching value + $testcase->mimic = $this->getMock('Mimic'); + $testcase->mimic->expects($this->once()) + ->method($method) + ->will($this->returnValue($return)); + + return $testcase; + } + + /** + * Gets a mock request with one method mocked to return a value for testing + * @param string $method + * @param mixed $return + * @return Request + */ + protected function _mock_request($method = NULL, $return = NULL) + { + // Mock a request + $request = $this->getMock('Request', array(),array(), '', FALSE); + + // Mock a method if required + if ($method !== NULL) + { + $request->expects($this->once()) + ->method($method) + ->will($this->returnValue($return)); + } + + return $request; + } + + public function provider_should_assert_mimic_request_count() + { + return array( + array(1, 1, TRUE), + array(1, 0, FALSE) + ); + } + + /** + * @dataProvider provider_should_assert_mimic_request_count + * @param integer $mock_count + * @param integer $expected + * @param boolean $should_pass + */ + public function test_should_assert_mimic_request_count($mock_count, $expected, $should_pass) + { + $testcase = $this->_testcase_with_mock_mimic('request_count', $mock_count); + + $this->_test_assertion($testcase, 'assertMimicRequestCount', array($expected), $should_pass); + } + + public function provider_should_assert_last_request_url() + { + return array( + array('http://www.foo.bar/abc', 'http://www.foo.bar/abc', TRUE), + array('http://www.foo.bar/abc', 'http://www.foo.bar/foo', FALSE) + ); + } + + /** + * @dataProvider provider_should_assert_last_request_url + * @param string $mock_url + * @param integer $expected + * @param boolean $should_pass + */ + public function test_should_assert_last_request_url($mock_url, $expected, $should_pass) + { + $testcase = $this->_testcase_with_mock_mimic('last_request', + $this->_mock_request('uri', $mock_url)); + + $this->_test_assertion($testcase, 'assertMimicLastRequestURL', array($expected), $should_pass); + } + + public function provider_should_assert_last_request_method() + { + return array( + array('GET', 'GET', TRUE), + array('GET', 'POST', FALSE) + ); + } + + /** + * @dataProvider provider_should_assert_last_request_method + * @param string $mock_method + * @param integer $expected + * @param boolean $should_pass + */ + public function test_should_assert_last_request_method($mock_method, $expected, $should_pass) + { + $testcase = $this->_testcase_with_mock_mimic('last_request', + $this->_mock_request('method', $mock_method)); + + $this->_test_assertion($testcase, 'assertMimicLastRequestMethod', array($expected), $should_pass); + } + + public function provider_should_assert_last_request_header() + { + return array( + array('X-foo', 'bar', 'bar', TRUE), + array('X-foo', 'bar', 'biddy', FALSE) + ); + } + + /** + * @dataProvider provider_should_assert_last_request_header + * @param string $header + * @param string $mock_value + * @param integer $expected + * @param boolean $should_pass + */ + public function test_should_assert_last_request_header($header, $mock_value, $expected, $should_pass) + { + $request = $this->_mock_request(); + $request->expects($this->once()) + ->method('headers') + ->with($header) + ->will($this->returnValue($mock_value)); + + $testcase = $this->_testcase_with_mock_mimic('last_request', + $request); + + $this->_test_assertion($testcase, 'assertMimicLastRequestHeader', array($header, $expected), $should_pass); + } + + public function provider_should_assert_last_request_query() + { + return array( + array('foo', 'bar', 'bar', TRUE), + array('foo', 'bar', 'biddy', FALSE) + ); + } + + /** + * @dataProvider provider_should_assert_last_request_query + * @param string $key + * @param string $mock_value + * @param integer $expected + * @param boolean $should_pass + */ + public function test_should_assert_last_request_query($key, $mock_value, $expected, $should_pass) + { + $request = $this->_mock_request(); + $request->expects($this->once()) + ->method('query') + ->with($key) + ->will($this->returnValue($mock_value)); + + $testcase = $this->_testcase_with_mock_mimic('last_request', + $request); + + $this->_test_assertion($testcase, 'assertMimicLastRequestQuery', array($key, $expected), $should_pass); + } + + public function provider_should_assert_last_request_body() + { + return array( + array('body-foo', 'body-foo', TRUE), + array('body-foo', 'body-bar', FALSE) + ); + } + + /** + * @dataProvider provider_should_assert_last_request_body + * @param string $mock_body + * @param integer $expected + * @param boolean $should_pass + */ + public function test_should_assert_last_request_body($mock_body, $expected, $should_pass) + { + $testcase = $this->_testcase_with_mock_mimic('last_request', + $this->_mock_request('body', $mock_body)); + + $this->_test_assertion($testcase, 'assertMimicLastRequestBody', array($expected), $should_pass); + } + + } /** From 6303b2aa3392467bafde2bef1a550957c6b4fcee Mon Sep 17 00:00:00 2001 From: Andrew Coulton Date: Thu, 5 Jan 2012 09:10:58 +0000 Subject: [PATCH 07/11] Added AssertRequestsContains and AssertRequestsNotContains - closes #4 --- classes/mimic/unittest/testcase.php | 60 ++++++++++++++++++++ tests/mimic/TestCaseTest.php | 88 +++++++++++++++++++++++++++++ 2 files changed, 148 insertions(+) diff --git a/classes/mimic/unittest/testcase.php b/classes/mimic/unittest/testcase.php index ec31110..ee4b126 100644 --- a/classes/mimic/unittest/testcase.php +++ b/classes/mimic/unittest/testcase.php @@ -100,4 +100,64 @@ public function assertMimicLastRequestBody($expected) $this->assertEquals($expected, $this->mimic->last_request()->body()); } + /** + * Searches the request history for a request to the given URL and (optionally) + * with the specified method, returning TRUE or FALSE. + * + * @param string $url + * @param string $method + * @return boolean + */ + protected function _search_request_history($url, $method) + { + foreach ($this->mimic->request_history() as $request) + { + if (($method !== NULL) AND ($method !== $request->method())) + { + continue; + } + + if ($request->uri() === $url) + { + return TRUE; + } + } + + // Nothing found + return FALSE; + + } + + /** + * Verify that a request (optionally with a specified method) was made to the + * given URL at some point in execution - for requests where the sequence doesn't + * matter, this is obviously less brittle than asserting a specific request. + * + * @param string $url + * @param string $method + */ + public function assertMimicRequestsContains($url, $method = NULL) + { + if ( ! $this->_search_request_history($url, $method)) + { + $this->fail("Expected $method request to $url and none was made"); + } + } + + /** + * Verify that a request (optionally with a specified method) was not made to + * the given URL at any point in execution - for requests where the sequence doesn't + * matter, this is obviously less brittle than asserting a specific request. + * + * @param string $url + * @param string $method + */ + public function assertMimicRequestsNotContains($url, $method = NULL) + { + if ($this->_search_request_history($url, $method)) + { + $this->fail("A $method request was made to $url"); + } + } + } \ No newline at end of file diff --git a/tests/mimic/TestCaseTest.php b/tests/mimic/TestCaseTest.php index 9e40b46..8e0d57f 100644 --- a/tests/mimic/TestCaseTest.php +++ b/tests/mimic/TestCaseTest.php @@ -88,6 +88,15 @@ protected function _test_assertion($testcase, $method, $args, $should_pass) } return; } + catch (PHPUnit_Framework_AssertionFailedError $e) + { + // Check that this is expected to fail + if ($should_pass) + { + $this->fail('Unexpected assertion failure with message '.$e->getMessage()); + } + return; + } // Check that it is expected to pass if ( ! $should_pass) @@ -282,6 +291,85 @@ public function test_should_assert_last_request_body($mock_body, $expected, $sho $this->_test_assertion($testcase, 'assertMimicLastRequestBody', array($expected), $should_pass); } + public function provider_should_assert_or_not_requests_contains() + { + $requests = array( + array('method'=>'GET', 'uri' => 'http://foo.bar.com/foo'), + array('method'=>'POST', 'uri' => 'http://foo.bar.com/foo'), + array('method'=>'GET', 'uri' => 'http://foo.bar.com/bar'), + ); + return array( + array($requests, 'http://foo.bar.com/foo', 'POST', TRUE), + array($requests, 'http://foo.bar.com/foo', NULL, TRUE), + array($requests, 'http://foo.bar.com/foo', 'PUT', FALSE), + array($requests, 'http://foo.bar.com/bar', 'GET', TRUE), + ); + } + + /** + * Helper method, as the test strap for assertMimicRequestsContains and + * assertMimicRequestsNotContains is very similar. + * + * @param string $method + * @param array $mock_requests + * @param string $test_url + * @param string $test_method + * @param string $should_pass + */ + protected function _test_should_or_not_contain($method, $mock_requests, $test_url, $test_method, $should_pass) + { + // Build an array of mock requests for the stack + $request_history = array(); + foreach ($mock_requests as $mock_request) + { + $request = $this->_mock_request(); + + // Request::uri() will only be called if the method matches the search + $request->expects($this->any()) + ->method('uri') + ->will($this->returnValue($mock_request['uri'])); + + // Request::method() will only be called if matching on it + $request->expects($this->any()) + ->method('method') + ->will($this->returnValue($mock_request['method'])); + $request_history[] = $request; + } + + // Mock the testcase + $testcase = $this->_testcase_with_mock_mimic('request_history', $request_history); + + // Call and test the assertion + $this->_test_assertion($testcase, $method, array($test_url, $test_method), $should_pass); + } + + /** + * @dataProvider provider_should_assert_or_not_requests_contains + * @param array $mock_requests + * @param string $test_url + * @param string $test_method + * @param boolean $should_contain Whether the stack should contain the request + */ + public function test_should_assert_requests_contains($mock_requests, $test_url, $test_method, $should_contain) + { + $this->_test_should_or_not_contain('assertMimicRequestsContains', + $mock_requests, $test_url, $test_method, $should_contain); + } + + /** + * @dataProvider provider_should_assert_or_not_requests_contains + * @param array $mock_requests + * @param string $test_url + * @param string $test_method + * @param boolean $should_contain Whether the stack should contain the request + */ + public function test_should_assert_requests_not_contains($mock_requests, $test_url, $test_method, $should_contain) + { + $should_pass = ! $should_contain; + $this->_test_should_or_not_contain('assertMimicRequestsNotContains', + $mock_requests, $test_url, $test_method, $should_pass); + } + } From dc0fdda6aedd38c96eae2db0d7ef4f88b6803585 Mon Sep 17 00:00:00 2001 From: Andrew Coulton Date: Thu, 5 Jan 2012 13:40:44 +0000 Subject: [PATCH 08/11] Docs for the base unittest testcase - closes #4 --- guide/mimic/testing.md | 125 +++++++++++++++++++++++++++++------------ 1 file changed, 89 insertions(+), 36 deletions(-) diff --git a/guide/mimic/testing.md b/guide/mimic/testing.md index c94f905..329d937 100644 --- a/guide/mimic/testing.md +++ b/guide/mimic/testing.md @@ -23,17 +23,33 @@ application code accordingly. ## Basic usage with PHPUnit -For basic usage with PHPUnit, you could simply build a test case like this: +For most common testing, Mimic comes bundled with a [Mimic_Unittest_Testcase] +base class which provides a useful set of assertions and activates Mimic with +a clean stack in the setUp method. You can of course easily incorporate Mimic within +any test method or class, for example if it's only required for a few of your tests. - class APITest extends Unittest_Testcase +You could therefore simply build a test case like this: + + class APITest extends Mimic_Unittest_Testcase { - public static function setUpBeforeClass() + + /** + * Mimic will set the scenario name to this during setUp (if not NULL) + * @var string + */ + protected $_mimic_default_scenario = 'api_foo'; + + public static function setUp() { - // Activate Mimic and set recording mode from environment var - // You can equally just use the config from your config files - Mimic::instance() - ->enable_recording(Arr::get($_SERVER, 'MIMIC_RECORD', FALSE)); - ->enable_updating(Arr::get($_SERVER, 'MIMIC_UPDATE', FALSE)); + parent::setUp(); + + /* + * If you want to set recording and updating mode from environment variables (may be + * useful for ease) rather than config, you can do something like this. Otherwise, the + * base setUp() will do everything you need. + */ + $this->mimic->enable_recording(Arr::get($_SERVER,'MIMIC_ENABLE_RECORD', FALSE) ? TRUE : FALSE); + $this->mimic->enable_updating(Arr::get($_SERVER,'MIMIC_ENABLE_UPDATE', FALSE) ? TRUE : FALSE); } /** @@ -65,32 +81,26 @@ high-level behaviour. You'll probably want to test the observable external actio of your code - for example verifying that your code is not calling more API methods than you expect, or that reading a resource is not making changes to it. -Mimic provides a number of methods that allow you to access and inspect the requests -your application code has made. +The [Mimic_Unittest_Testcase] base class provides a set of useful assertion methods to +verify common expectations. -Method | Returns / Action ------------------------------|-------- -[Mimic::last_request] | [Request] object for the most recently executed request -[Mimic::request_count] | The number of requests that have been executed -[Mimic::request_history]\(id)| An array of requests (or a single element) in order executed -[Mimic::reset_requests] | Reset the request stack for a fresh test +Method | Tests +------------------------------|-------- +assertMimicRequestCount | An expected number of requests were made +assertMimicLastRequestURL | The last request URL +assertMimicLastRequestMethod | The last request method +assertMimicLastRequestHeader | A header value in the last request +assertMimicLastRequestQuery | A query value in the last request +assertMimicLastRequestBody | The body-content of the last request +assertMimicRequestsContains | At least one request was made to a given URL (optionally, with a given method) +assertMimicRequestsNotContains| No requests were made to the given URL (optionally, with a given method) -[!!] Mimic operates as a singleton by default (as we can't inject an instance into -the [Mimic_Request_Client]) so you need to call [Mimic::reset_requests] before -each test - the easiest way to do this is in your setUp() method. - -So, your unit test code might look like this: +### Testing individual requests +To build on the test above, you could add the following to your testcase: - class APITest extends Unittest_Testcase + class APITest extends Mimic_Unittest_Testcase { - protected $_mimic = NULL; - - public function setUp() - { - $this->_mimic = Mimic::instance(); - $this->_mimic->reset_requests(); - } - + public function test_api_gets_version() { $api = new MyApi; @@ -100,14 +110,57 @@ So, your unit test code might look like this: */ $version = $api->get_version(); $this->assertEquals('1.2', $version); - - // Verify expected web request calls - $this->assertEquals(1, $this->_mimic->request_count()); - $last_request = $this->_mimic->last_request(); - $this->assertEquals('http://my.api.com/version', $last_request->uri()); - $this->assertEquals('GET', $last_request()->method()); + + // Check the web request happened + $this->assertMimicRequestCount(1); + $this->assertMimicLastRequestURL('http://my.api.com/version'); + $this->assertMimicLastRequestMethod('GET'); } } +### Requests (not)? contains +Obviously, assertMimicRequestsContains and assertMimicRequestsNotContains are less powerful than the tests against +last request, but are also less brittle over time and allow many simple checks. For example, perhaps you want to +establish that an API call only makes a connection if the client has supplied authentication. You'll obviously +want to verify that the method throws an exception (or whatever your logic flow requires). But you may also want +to be sure that the exception was thrown before the request was sent - in that case you could test: + + public function test_api_only_deletes_if_authenticated() + { + $api = new MyApi; + + try + { + $api->delete(5); + } + catch(MyApi_Exception_Not_Authenticated $e) + { + $this->assertMimicRequestsNotContains('http://api.foo.thing/5', 'DELETE'); + return; + } + + $this->fail('Expected MyApi_Exception_Not_Authenticated was not thrown'); + } + +### More complex tests + +For other expectations, Mimic provides a number of methods that allow you to access and +inspect the requests your application code has made. + +Method | Returns / Action +-----------------------------|-------- +[Mimic::last_request] | [Request] object for the most recently executed request +[Mimic::request_count] | The number of requests that have been executed +[Mimic::request_history]\(id)| An array of requests (or a single element) in order executed +[Mimic::reset_requests] | Reset the request stack for a fresh test + +[!!] Mimic operates as a singleton by default (as we can't inject an instance into +the [Mimic_Request_Client]) so you need to call [Mimic::reset_requests] before +each test - the easiest way to do this is in your setUp() method. The [Mimic_Unittest_Testcase] +base class handles this for you. + +If you find you're regularly using the same custom assertions, please do [send a pull request](https://github.com/acoulton/mimic/pulls) +for inclusion in the base test cases. + --- Continue to [Updating Definitions](updating) \ No newline at end of file From 94fe5c3031bda5d50bf84d47adc01252d54930a3 Mon Sep 17 00:00:00 2001 From: Erkan Yilmaz Date: Sat, 10 Mar 2012 09:49:20 +0100 Subject: [PATCH 09/11] fix 404 errors (file names have extension .md) --- guide/mimic/index.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/guide/mimic/index.md b/guide/mimic/index.md index e1c6362..69af984 100644 --- a/guide/mimic/index.md +++ b/guide/mimic/index.md @@ -5,12 +5,12 @@ web services. If you've used the [VCR module for Ruby](https://github.com/myronmarston/vcr), you'll recognise the concept. By default, [Mimic] intercepts all external requests -and throws an exception. When [recording mode](recording) is enabled, [Mimic] executes the +and throws an exception. When [recording mode](recording.md) is enabled, [Mimic] executes the external request and records the response (complete with headers and response status) to disk. Future requests to the external resource will return the response that has been stored on disk, allowing increased performance and more importantly an idempotent [implementation of unit and functional -tests](testing) with a minimum of configuration or mocking code. +tests](testing.md) with a minimum of configuration or mocking code. ## Scenarios Mimic supports multiple named scenarios (nothing more complex than a separate @@ -30,13 +30,13 @@ By default, Mimic matches all of: * URI Parameters * Request Headers -However, [matching rules can be easily edited](matching) to allow looser +However, [matching rules can be easily edited](matching.md) to allow looser matching of requests which can be useful if you want to return the same response for multiple requests - for example to minimise the effort involved in supporting query parameters that aren't relevant to your test scenario. ## Customising responses -Requests and responses are stored in [easily editable formats](customising), +Requests and responses are stored in [easily editable formats](customising.md), allowing you to customise both the request and response for a variety of scenarios. For example, you might want to edit the response to simulate an error condition that is difficult to trigger from the client side. @@ -47,14 +47,14 @@ authentication tokens or private content before committing to a source code repository!** Mimic aids review, source control and editing of responses by passing supported -content types (currently XML and JSON) through a [formatter](formatters) before +content types (currently XML and JSON) through a [formatter](formatters.md) before saving. Formatters "pretty-print" the responses, introducing newlines, indentation, and whitespace to make responses human-readable. ## Verifying application behaviour In addition to replaying "canned" responses, Mimic keeps a history of requests executed and responses returned. You can -[access the history from your test cases](testing#verifying-expectations) +[access the history from your test cases](testing.md#verifying-expectations) to verify: * That an expected pattern of requests were sent (for example, that a given @@ -68,4 +68,4 @@ to verify: * PHP 5.3 or greater If you want to run the unit tests, you will need PHPUnit and the -[vfsStream](https://github.com/mikey179/vfsStream) virtual filesystem library. \ No newline at end of file +[vfsStream](https://github.com/mikey179/vfsStream) virtual filesystem library. From 01e5086e6aaa5e25d6848ffe844eb923496cfcd4 Mon Sep 17 00:00:00 2001 From: Erkan Yilmaz Date: Sat, 10 Mar 2012 09:53:20 +0100 Subject: [PATCH 10/11] here also add extension .md, otherwise 404 errors while reading online --- guide/mimic/config.md | 6 +++--- guide/mimic/customising.md | 6 +++--- guide/mimic/matching.md | 2 +- guide/mimic/menu.md | 14 +++++++------- guide/mimic/recording.md | 4 ++-- guide/mimic/testing.md | 8 ++++---- guide/mimic/updating.md | 4 ++-- 7 files changed, 22 insertions(+), 22 deletions(-) diff --git a/guide/mimic/config.md b/guide/mimic/config.md index 77e98ba..f15a724 100644 --- a/guide/mimic/config.md +++ b/guide/mimic/config.md @@ -12,11 +12,11 @@ debug_headers | FALSE | Add X-Mimic debug headers to enable_recording| FALSE | Allow recording of requests that aren't matched? enable_updating | FALSE | Update matched requests by executing and storing new response? external_client | NULL | Specify an external client to use when executing requests - if NULL will use whatever is set for [Request_Client_External]::$client -response_formatters | array | An array of [formatters](formatters) to use for given content-types +response_formatters | array | An array of [formatters](formatters.md) to use for given content-types ## Default formatters -As shipped, Mimic uses the following default [formatters](formatters): +As shipped, Mimic uses the following default [formatters](formatters.md): Content-Type | Formatter -------------------------|------------ @@ -31,4 +31,4 @@ application/rss+xml |[Mimic_Response_Formatter_XML] * (others) |[Mimic_Response_Formatter] --- -Continue to [Response Formatters](formatters) \ No newline at end of file +Continue to [Response Formatters](formatters.md) diff --git a/guide/mimic/customising.md b/guide/mimic/customising.md index c4f0d90..2e5a889 100644 --- a/guide/mimic/customising.md +++ b/guide/mimic/customising.md @@ -70,7 +70,7 @@ straightforward: ) ))); -For more on how requests are matched, see [the section on request matching](matching). +For more on how requests are matched, see [the section on request matching](matching.md). ## Response bodies @@ -82,10 +82,10 @@ purposes if required. Likewise the same response body can be used by multiple request definitions (for example, if different response headers are required but the body is the same). -Response bodies are stored using [response formatters](formatters) which apply an +Response bodies are stored using [response formatters](formatters.md) which apply an appropriate file extension for the content type and can convert the content to a human readable format (for example, adding newlines and indentation to JSON responses) for easier editing and verification. --- -Continue to [Testing with Mimic](testing) \ No newline at end of file +Continue to [Testing with Mimic](testing.md) diff --git a/guide/mimic/matching.md b/guide/mimic/matching.md index d08751d..2168375 100644 --- a/guide/mimic/matching.md +++ b/guide/mimic/matching.md @@ -131,4 +131,4 @@ wildcards later. ); --- -Continue to [Customising Responses](customising) \ No newline at end of file +Continue to [Customising Responses](customising.md) diff --git a/guide/mimic/menu.md b/guide/mimic/menu.md index 4a1dff0..1a8e989 100644 --- a/guide/mimic/menu.md +++ b/guide/mimic/menu.md @@ -1,8 +1,8 @@ ## [Mimic]() -- [Recording](recording) -- [Matching Requests](matching) -- [Customising Responses](customising) -- [Testing with Mimic](testing) -- [Updating definitions](updating) -- [Configuration](config) -- [Response Formatters](formatters) \ No newline at end of file +- [Recording](recording.md) +- [Matching Requests](matching.md) +- [Customising Responses](customising.md) +- [Testing with Mimic](testing.md) +- [Updating definitions](updating.md) +- [Configuration](config.md) +- [Response Formatters](formatters.md) diff --git a/guide/mimic/recording.md b/guide/mimic/recording.md index ab608e2..3686c16 100644 --- a/guide/mimic/recording.md +++ b/guide/mimic/recording.md @@ -24,7 +24,7 @@ of the request and the response) and save it to disk for customisation and futur use. To use this feature, you'll need to enable recording mode either in your -[configuration](config) or by setting [Mimic::enable_recording]\(TRUE). +[configuration](config.md) or by setting [Mimic::enable_recording]\(TRUE). // Enable recording Mimic::instance()->enable_recording(TRUE); @@ -34,4 +34,4 @@ To use this feature, you'll need to enable recording mode either in your ->execute(); --- -Continue to [Matching Requests](matching) \ No newline at end of file +Continue to [Matching Requests](matching.md) diff --git a/guide/mimic/testing.md b/guide/mimic/testing.md index 329d937..5b3515c 100644 --- a/guide/mimic/testing.md +++ b/guide/mimic/testing.md @@ -10,14 +10,14 @@ The usual workflow would be: * Build your unit tests to excercise your application code, and enable Mimic in the setUp method of your tests. -* Run the unit tests with [recording](recording) enabled to allow Mimic to record the real-world +* Run the unit tests with [recording](recording.md) enabled to allow Mimic to record the real-world behaviour of the external services you are accessing. -* [Customise](customising) and tidy up your request and response definitions - +* [Customise](customising.md) and tidy up your request and response definitions - remembering to remove any sensitive details. * Commit the tests and scenarios to source control, for later re-use on other developer machines, CI servers, etc. * If and when external APIs change (or you could schedule a regular check) re-run -the tests with [updating](updating) enabled to automatically refresh your +the tests with [updating](updating.md) enabled to automatically refresh your definitions with new response formats and content, updating your unit tests and application code accordingly. @@ -163,4 +163,4 @@ If you find you're regularly using the same custom assertions, please do [send a for inclusion in the base test cases. --- -Continue to [Updating Definitions](updating) \ No newline at end of file +Continue to [Updating Definitions](updating.md) diff --git a/guide/mimic/updating.md b/guide/mimic/updating.md index bff2917..82e43c6 100644 --- a/guide/mimic/updating.md +++ b/guide/mimic/updating.md @@ -23,7 +23,7 @@ recording the details of exactly what was executed to generate the returned resp ## Enabling updating To enable updating mode, simply call [Mimic::enable_updating]\(TRUE) or set the -appropriate value in your [configuration](config). +appropriate value in your [configuration](config.md). --- -Continue to [Configuration](config) \ No newline at end of file +Continue to [Configuration](config.md) From 3f2016337d1b870efa2fe03c35f029ccace148d1 Mon Sep 17 00:00:00 2001 From: Andrew Coulton Date: Thu, 15 Mar 2012 17:49:09 +0000 Subject: [PATCH 11/11] Added changelog --- CHANGELOG.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..ac1ee95 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,20 @@ +# Mimic for PHP +#(c) 2012 Andrew Coulton +# Changelog + +## v0.6 + +### New features +* Use .md extension for userguide links so that they can be read on github +(requires latest version of Kohana 3.2 to browse in the userguide module) +* Provides a base unittest testcase class for common assertions and requirements (see issue #4) +* + +### Bugfixes +* Explicitly sets Request content-length header if not present - fixes #5 +* Only requires vfsStream to run Mimic's own unit tests - previously caused an +error if trying to run other testcases in the project without vfsStream installed. + +## v0.5 + +* First version \ No newline at end of file