├── .gitignore ├── .gitmodules ├── .travis.yml ├── LICENSE ├── README.md ├── codeception.yml ├── composer.json ├── docker-compose.yml ├── src ├── Lib │ └── Connector │ │ └── Slim.php └── Module │ └── Slim.php └── tests ├── _bootstrap.php ├── _data ├── container.php ├── dump.sql └── template.phtml ├── _output └── .gitignore ├── _support ├── FunctionalTester.php ├── Helper │ └── Functional.php └── _generated │ └── .gitignore ├── functional.suite.yml └── functional ├── ClickCept.php ├── FileUploadWithObjectCept.php ├── RequestAndResponseAreFromContainerCept.php ├── SeeCept.php ├── SeeRawAndParsedBodyIsNotNullCept.php ├── SeeResponseCodeIsCept.php └── _bootstrap.php /.gitignore: -------------------------------------------------------------------------------- 1 | # IDE 2 | .idea 3 | 4 | # Composer 5 | composer.lock 6 | vendor 7 | 8 | # Runtime 9 | storages 10 | docker-compose.*.yml 11 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "tests/functional/REST"] 2 | path = tests/functional/REST 3 | url = git@github.com:Naktibalda/codeception-rest-testcase.git 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - '5.6' 5 | - '7.0' 6 | - '7.1' 7 | - nightly 8 | - hhvm 9 | 10 | # disable the default submodule logic 11 | git: 12 | submodules: false 13 | 14 | before_install: 15 | - composer self-update 16 | 17 | install: 18 | - composer install 19 | 20 | # use sed to replace the SSH URL with the public URL, then init and update submodules 21 | before_script: 22 | - sed -i 's/git@github.com:/git:\/\/github.com\//' .gitmodules 23 | - git submodule update --init --recursive 24 | 25 | script: 26 | - vendor/bin/codecept run 27 | 28 | matrix: 29 | fast_finish: true 30 | allow_failures: 31 | - php: nightly 32 | 33 | sudo: false 34 | 35 | cache: 36 | directories: 37 | - vendor 38 | - $HOME/.composer/cache -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Herloct 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Codeception Slim Module 2 | 3 | [![Master Build Status](https://travis-ci.org/herloct/codeception-slim-module.svg?branch=master)](https://travis-ci.org/herloct/codeception-slim-module) 4 | [![Packagist Stable Version](https://img.shields.io/packagist/v/herloct/codeception-slim-module.svg)](https://packagist.org/packages/herloct/codeception-slim-module) 5 | [![Packagist License](https://img.shields.io/packagist/l/herloct/codeception-slim-module.svg)](https://packagist.org/packages/herloct/codeception-slim-module) 6 | [![Libraries.io for GitHub](https://img.shields.io/librariesio/github/herloct/codeception-slim-module.svg)](https://libraries.io/github/herloct/codeception-slim-module) 7 | 8 | This module allows you to run tests inside [Slim 3 Microframework](http://www.slimframework.com/). 9 | Based on [ZendExpressive Module](https://github.com/Codeception/Codeception/blob/2.2/src/Codeception/Module/ZendExpressive.php). 10 | 11 | ## Install 12 | 13 | Via commandline: 14 | 15 | ```shell 16 | composer require --dev herloct/codeception-slim-module 17 | ``` 18 | 19 | Via `composer.json`: 20 | 21 | ```json 22 | { 23 | "require-dev": { 24 | "herloct/codeception-slim-module": "^1.1" 25 | } 26 | } 27 | ``` 28 | 29 | ## Config 30 | 31 | Put this on your `codeception.yml` 32 | 33 | ```yaml 34 | modules: 35 | config: 36 | \Herloct\Codeception\Module\Slim: 37 | container: path/to/container.php 38 | REST: 39 | depends: \Herloct\Codeception\Module\Slim 40 | ``` 41 | 42 | Or on your `tests/functional.suite.yml` 43 | 44 | ```yaml 45 | modules: 46 | enabled: 47 | - \Helper\Functional 48 | - \Herloct\Codeception\Module\Slim: 49 | container: path/to/container.php 50 | - REST: 51 | depends: \Herloct\Codeception\Module\Slim 52 | ``` 53 | 54 | The `container` properties is a relative path to file which returns your App's Container. 55 | Here is the minimum `container.php` contents. 56 | 57 | ```php 58 | require __DIR__.'/vendor/autoload.php'; 59 | 60 | use Psr\Container\ContainerInterface; 61 | use Slim\App; 62 | use Slim\Container; 63 | 64 | $container = new Container([ 65 | App::class => function (ContainerInterface $c) { 66 | $app = new App($c); 67 | 68 | // routes and middlewares here 69 | 70 | return $app; 71 | } 72 | ]); 73 | 74 | return $container; 75 | ``` 76 | 77 | You could use this [Sample Project](https://github.com/herloct/codeception-slim-module-example) as a reference. 78 | 79 | ## API 80 | 81 | * app - instance of `\Slim\App` 82 | * container - instance of `\Psr\Container\ContainerInterface` 83 | * client - [BrowserKit](http://symfony.com/doc/current/components/browser_kit.html) client 84 | 85 | ## Todos 86 | 87 | * Add more acceptance/functional tests other than REST. 88 | -------------------------------------------------------------------------------- /codeception.yml: -------------------------------------------------------------------------------- 1 | actor: Tester 2 | paths: 3 | tests: tests 4 | log: tests/_output 5 | data: tests/_data 6 | support: tests/_support 7 | envs: tests/_envs 8 | settings: 9 | bootstrap: _bootstrap.php 10 | colors: true 11 | memory_limit: 1024M 12 | extensions: 13 | enabled: 14 | - Codeception\Extension\RunFailed 15 | coverage: 16 | enabled: true 17 | remote: false 18 | include: 19 | - src/* 20 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "herloct/codeception-slim-module", 3 | "type": "library", 4 | "description": "Codeception Module for Slim 3 Microframework.", 5 | "keywords": ["codeception", "module", "slim"], 6 | "homepage": "https://github.com/herloct/codeception-slim-module", 7 | "license": "MIT", 8 | "authors": [ 9 | { 10 | "name": "Herloct", 11 | "email": "herloct@gmail.com" 12 | } 13 | ], 14 | "minimum-stability": "stable", 15 | "require": { 16 | "php": "^5.6 || ^7.0" 17 | }, 18 | "require-dev": { 19 | "php": "^5.6 || ^7.0", 20 | "codeception/codeception": "^2.2", 21 | "slim/slim": "^3.5" 22 | }, 23 | "autoload": { 24 | "psr-4": { 25 | "Herloct\\Codeception\\": "src" 26 | } 27 | }, 28 | "autoload-dev": { 29 | "classmap": ["tests/_support", "tests/functional"] 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | composer: 4 | image: herloct/composer:1.3.2-php5.6 5 | volumes: 6 | - .:/project 7 | 8 | codecept: 9 | image: php:5.6.30 10 | working_dir: /project 11 | entrypoint: 12 | - php 13 | - vendor/bin/codecept 14 | volumes: 15 | - .:/project 16 | 17 | phpcbf: 18 | image: herloct/phpcbf:2.8.1 19 | volumes: 20 | - .:/project 21 | -------------------------------------------------------------------------------- /src/Lib/Connector/Slim.php: -------------------------------------------------------------------------------- 1 | app = $app; 33 | } 34 | 35 | /** 36 | * Makes a request. 37 | * 38 | * @param BrowserKitRequest $request An origin request instance 39 | * 40 | * @return BrowserKitResponse An origin response instance 41 | */ 42 | protected function doRequest($request) 43 | { 44 | $slimRequest = $this->convertRequest($request); 45 | 46 | $container = $this->app->getContainer(); 47 | 48 | /* @var $slimResponse ResponseInterface */ 49 | $slimResponse = $container->get('response'); 50 | 51 | // reset body stream 52 | $slimResponse = $slimResponse->withBody(new Stream(fopen('php://temp', 'w+'))); 53 | 54 | $slimResponse = $this->app->process($slimRequest, $slimResponse); 55 | 56 | return new BrowserKitResponse( 57 | (string) $slimResponse->getBody(), 58 | $slimResponse->getStatusCode(), 59 | $slimResponse->getHeaders() 60 | ); 61 | } 62 | 63 | /** 64 | * Convert to PSR-7's ServerRequestInterface. 65 | * 66 | * @param BrowserKitRequest $request 67 | * @return ServerRequestInterface 68 | */ 69 | private function convertRequest(BrowserKitRequest $request) 70 | { 71 | $environment = Environment::mock($request->getServer()); 72 | $uri = Uri::createFromString($request->getUri()); 73 | $headers = Headers::createFromEnvironment($environment); 74 | $cookies = Cookies::parseHeader($headers->get('Cookie', [])); 75 | 76 | $container = $this->app->getContainer(); 77 | 78 | /* @var $slimRequest ServerRequestInterface */ 79 | $slimRequest = $container->get('request'); 80 | 81 | $slimRequest = $slimRequest->withMethod($request->getMethod()) 82 | ->withUri($uri) 83 | ->withUploadedFiles($this->convertFiles($request->getFiles())) 84 | ->withCookieParams($cookies); 85 | 86 | foreach ($headers->keys() as $key) { 87 | $slimRequest = $slimRequest->withHeader($key, $headers->get($key)); 88 | } 89 | 90 | if ($request->getContent() !== null) { 91 | $body = new RequestBody(); 92 | $body->write($request->getContent()); 93 | $slimRequest = $slimRequest 94 | ->withBody($body); 95 | } 96 | 97 | $parsed = []; 98 | if ($request->getMethod() !== 'GET') { 99 | $parsed = $request->getParameters(); 100 | } 101 | 102 | // make sure we do not overwrite a request with a parsed body 103 | if (!$slimRequest->getParsedBody()) { 104 | $slimRequest = $slimRequest 105 | ->withParsedBody($parsed); 106 | } 107 | 108 | return $slimRequest; 109 | } 110 | 111 | /** 112 | * Convert to PSR-7's UploadedFileInterface. 113 | * 114 | * @param array $files 115 | * @return array 116 | */ 117 | private function convertFiles(array $files) 118 | { 119 | $fileObjects = []; 120 | foreach ($files as $fieldName => $file) { 121 | if ($file instanceof UploadedFileInterface) { 122 | $fileObjects[$fieldName] = $file; 123 | } elseif (!isset($file['tmp_name']) && !isset($file['name'])) { 124 | $fileObjects[$fieldName] = $this->convertFiles($file); 125 | } else { 126 | $fileObjects[$fieldName] = new UploadedFile( 127 | $file['tmp_name'], 128 | $file['name'], 129 | $file['type'], 130 | $file['size'], 131 | $file['error'] 132 | ); 133 | } 134 | } 135 | return $fileObjects; 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/Module/Slim.php: -------------------------------------------------------------------------------- 1 | container = include Configuration::projectDir() . $this->config['container']; 31 | chdir($cwd); 32 | 33 | $this->app = $this->container->get(App::class); 34 | 35 | parent::_initialize(); 36 | } 37 | 38 | public function _before(TestInterface $test) 39 | { 40 | $this->client = new Connector(); 41 | $this->client->setApp($this->app); 42 | 43 | parent::_before($test); 44 | } 45 | 46 | public function _after(TestInterface $test) 47 | { 48 | if (session_status() === PHP_SESSION_ACTIVE) { 49 | session_write_close(); 50 | } 51 | 52 | parent::_after($test); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /tests/_bootstrap.php: -------------------------------------------------------------------------------- 1 | $uploadedFile) { 18 | /* @var $uploadedFile UploadedFile|array */ 19 | if (is_array($uploadedFile)) { 20 | $result[$fieldName] = files_to_array($uploadedFile); 21 | } else { 22 | $result[$fieldName] = [ 23 | 'name' => $uploadedFile->getClientFilename(), 24 | 'tmp_name' => ReflectionHelper::readPrivateProperty($uploadedFile, 'file'), 25 | 'size' => $uploadedFile->getSize(), 26 | 'type' => $uploadedFile->getClientMediaType(), 27 | 'error' => $uploadedFile->getError(), 28 | ]; 29 | } 30 | } 31 | 32 | return $result; 33 | } 34 | 35 | class NewRequest extends Request {} 36 | 37 | class NewResponse extends Response {} 38 | 39 | $container = new Container([ 40 | 'request' => function (ContainerInterface $c) { 41 | return NewRequest::createFromEnvironment($c->get('environment')); 42 | }, 43 | 44 | 'response' => function (ContainerInterface $c) { 45 | $headers = new Headers(['Content-Type' => 'text/html; charset=UTF-8']); 46 | $response = new NewResponse(200, $headers); 47 | 48 | return $response->withProtocolVersion($c->get('settings')['httpVersion']); 49 | }, 50 | 51 | App::class => function (ContainerInterface $c) { 52 | $app = new App($c); 53 | 54 | $app->get( 55 | '/api/ping', 56 | function (ServerRequestInterface $request, ResponseInterface $response) { 57 | $body = $response->getBody(); 58 | $body->write(json_encode([ 59 | 'ack' => time() 60 | ])); 61 | 62 | return $response->withHeader('content-type', 'application/json') 63 | ->withBody($body); 64 | } 65 | ); 66 | 67 | $app->map( 68 | ['GET', 'POST', 'PUT', 'DELETE'], 69 | '/rest', 70 | function (ServerRequestInterface $request, ResponseInterface $response) { 71 | $tokenHeaderValue = null; 72 | $tokenHeader = $request->getHeader('X-Auth-Token'); 73 | if (count($tokenHeader) > 0) { 74 | $tokenHeaderValue = $tokenHeader[0]; 75 | } 76 | 77 | $responseClass = get_class($response); 78 | $requestClass = get_class($request); 79 | 80 | $body = $response->getBody(); 81 | $body->write(json_encode([ 82 | 'requestMethod' => $request->getMethod(), 83 | 'requestUri' => $request->getRequestTarget(), 84 | 'queryParams' => $request->getQueryParams(), 85 | 'formParams' => $request->getParsedBody(), 86 | 'rawBody' => (string)$request->getBody(), 87 | 'headers' => $request->getHeaders(), 88 | 'X-Auth-Token' => $tokenHeaderValue, 89 | 'files' => files_to_array($request->getUploadedFiles()), 90 | 'responseClass' => $responseClass, 91 | 'requestClass' => $requestClass, 92 | ])); 93 | 94 | return $response->withHeader('content-type', 'application/json') 95 | ->withBody($body); 96 | } 97 | ); 98 | 99 | $app->get( 100 | '/', 101 | function (ServerRequestInterface $request, ResponseInterface $response) { 102 | $body = $response->getBody(); 103 | $body->write(file_get_contents(__DIR__.'/template.phtml')); 104 | 105 | return $response->withHeader('content-type', 'text/html') 106 | ->withBody($body); 107 | } 108 | ); 109 | 110 | return $app; 111 | } 112 | ]); 113 | 114 | return $container; 115 | -------------------------------------------------------------------------------- /tests/_data/dump.sql: -------------------------------------------------------------------------------- 1 | /* Replace this file with actual dump of your database */ -------------------------------------------------------------------------------- /tests/_data/template.phtml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Title 6 | 7 | 8 |

HTTP messages are the foundation of web development.

9 | Ping Test 10 | 11 | 12 | -------------------------------------------------------------------------------- /tests/_output/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /tests/_support/FunctionalTester.php: -------------------------------------------------------------------------------- 1 | wantTo('click a link and see a change in url'); 4 | 5 | $I->amOnPage('/'); 6 | $I->click('Ping Test'); 7 | $I->seeCurrentUrlEquals('/api/ping'); 8 | $I->see("ack"); -------------------------------------------------------------------------------- /tests/functional/FileUploadWithObjectCept.php: -------------------------------------------------------------------------------- 1 | wantTo('upload file'); 4 | 5 | $I->sendPOST('/rest', [], [ 6 | 'dump' => new \Slim\Http\UploadedFile(codecept_data_dir('dump.sql'), 'dump.sql', 'text/plain', 57, 0) 7 | ]); 8 | $I->seeResponseIsJson(); 9 | $I->seeResponseContainsJson(['files' => [ 10 | 'dump' => [ 11 | 'name' => 'dump.sql', 12 | 'size' => 57, 13 | ] 14 | ]]); -------------------------------------------------------------------------------- /tests/functional/RequestAndResponseAreFromContainerCept.php: -------------------------------------------------------------------------------- 1 | wantTo('test that the request and response objects are being created from the container'); 5 | $I->haveHttpHeader('content-type', 'application/json'); 6 | $I->sendPOST('/rest'); 7 | $I->seeResponseCodeIs(\Codeception\Util\HttpCode::OK); 8 | $I->seeResponseContainsJson([ 9 | 'responseClass' => 'NewResponse', 10 | 'requestClass' => 'NewRequest', 11 | ]); 12 | 13 | -------------------------------------------------------------------------------- /tests/functional/SeeCept.php: -------------------------------------------------------------------------------- 1 | wantTo('perform request and match text in response'); 4 | 5 | $I->amOnPage('/'); 6 | $I->seeResponseCodeIs(200); 7 | $I->see('HTTP messages are the foundation of web development.'); -------------------------------------------------------------------------------- /tests/functional/SeeRawAndParsedBodyIsNotNullCept.php: -------------------------------------------------------------------------------- 1 | '123']; 5 | $I->wantTo('test that the raw and parsed request body is returned in tact'); 6 | $I->haveHttpHeader('content-type', 'application/json'); 7 | $I->sendPOST('/rest', $data); 8 | $I->seeResponseCodeIs(\Codeception\Util\HttpCode::OK); 9 | $I->seeResponseContainsJson([ 10 | 'formParams' => $data, 11 | 'rawBody' => json_encode($data) 12 | ]); 13 | -------------------------------------------------------------------------------- /tests/functional/SeeResponseCodeIsCept.php: -------------------------------------------------------------------------------- 1 | wantTo('see different response code'); 4 | 5 | $I->amOnPage('/error'); 6 | $I->seeResponseCodeIs(404); -------------------------------------------------------------------------------- /tests/functional/_bootstrap.php: -------------------------------------------------------------------------------- 1 |