├── .editorconfig ├── .styleci.yml ├── LICENSE.md ├── README.md ├── composer.json └── src ├── AutoHttpTestsServiceProvider.php ├── TestGenerator.php └── app ├── Console └── Commands │ └── AutoHttpTest.php └── Http └── Middleware └── AutoHttpTests.php /.editorconfig: -------------------------------------------------------------------------------- 1 | ; This file is for unifying the coding style for different editors and IDEs. 2 | ; More information at http://editorconfig.org 3 | 4 | root = true 5 | 6 | [*] 7 | charset = utf-8 8 | indent_size = 4 9 | indent_style = space 10 | end_of_line = lf 11 | insert_final_newline = true 12 | trim_trailing_whitespace = true 13 | 14 | [*.md] 15 | trim_trailing_whitespace = false 16 | -------------------------------------------------------------------------------- /.styleci.yml: -------------------------------------------------------------------------------- 1 | preset: psr2 2 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Eduardo Aranda Hernandez 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 13 | > all 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 21 | > THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Laravel autohttptests 2 | 3 | [![Latest Version on Packagist][ico-version]][link-packagist] 4 | [![Software License][ico-license]](LICENSE.md) 5 | [![Total Downloads][ico-downloads]][link-downloads] 6 | 7 | **No more writing tests by hand =D** 8 | 9 | Just execute the command to record your actions as http tests 10 | 11 | ``` bash 12 | php artisan autohttptest:create 13 | 14 | ``` 15 | 16 | ![imagen](https://user-images.githubusercontent.com/4065733/31252701-a10f4580-a9e7-11e7-8b83-92cfc4b962f3.png) 17 | 18 | 19 | The command will **intercept** your requests and translate the response as a test. 20 | 21 | 22 | When finished, your test will be saved in tests/Feature/ 23 | 24 | ## Demo in video 25 | 26 | ![autohttptests-gif](https://user-images.githubusercontent.com/4065733/88353656-fcf9cc00-cd23-11ea-86b3-6c096378f540.gif) 27 | 28 | **What does it test?** 29 | 30 | - Request acting as same user 31 | - Make request using the same verb (GET,PUT,POST) with same arguments 32 | - Assert http response code 33 | - Assert errors 34 | - Assert redirection 35 | 36 | ## Example code 37 | 38 | 39 | ``` 40 | actingAs(\App\Models\User::find(1)) 51 | ->post('home/something', [ 52 | 'name' => 'a', 53 | 'lastname' => 'a', 54 | 'city' => '', 55 | 'hobbies' => '', 56 | 'twitter_username' => 'a', 57 | ]) 58 | ->assertStatus(302) 59 | ->assertSessionHasErrors([ 60 | 'name', 61 | 'country_id', 62 | 'twitter_username', 63 | ]); 64 | 65 | $this 66 | ->actingAs(\App\Models\User::find(1)) 67 | ->post('home/something', [ 68 | 'name' => 'asdfa', 69 | 'lastname' => 'asdfa', 70 | 'country_id' => '1', 71 | 'city' => '', 72 | 'hobbies' => '', 73 | 'twitter_username' => 'asdfa', 74 | ]) 75 | ->assertStatus(302) 76 | ->assertRedirect('home/something'); 77 | } 78 | } 79 | ``` 80 | 81 | **Note** 82 | 83 | Here we capture an unsuccessful post, with errors. 84 | Then, a **successful** post with redirection 85 | 86 | ## Install 87 | 88 | Via Composer 89 | 90 | ``` bash 91 | $ composer require eduardoarandah/autohttptests 92 | ``` 93 | 94 | If you are using laravel < 5.4 add to providers in config/app.php 95 | 96 | ``` 97 | EduardoArandaH\AutoHttpTests\AutoHttpTestsServiceProvider::class, 98 | ``` 99 | 100 | ## Usage 101 | 102 | ``` bash 103 | php artisan autohttptest:create 104 | 105 | ``` 106 | 107 | ## How does it work? 108 | 109 | when you run `php artisan autohttptest:create yourtest` it intercepts all requests and responses through a middleware. 110 | 111 | The request is then analyzed and transformed into a test in a file `storage/autohttptests.txt` 112 | 113 | If you're curious, you can see the building of that file in real time with 114 | 115 | ``` 116 | tail -f storage/autohttptests.txt 117 | ``` 118 | 119 | ## Credits 120 | 121 | - [Eduardo Aranda Hernandez][link-author] 122 | 123 | ## License 124 | 125 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 126 | 127 | [ico-version]: https://img.shields.io/packagist/v/eduardoarandah/autohttptests.svg?style=flat-square 128 | [ico-license]: https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square 129 | [ico-travis]: https://img.shields.io/travis/eduardoarandah/autohttptests/master.svg?style=flat-square 130 | [ico-scrutinizer]: https://img.shields.io/scrutinizer/coverage/g/eduardoarandah/autohttptests.svg?style=flat-square 131 | [ico-code-quality]: https://img.shields.io/scrutinizer/g/eduardoarandah/autohttptests.svg?style=flat-square 132 | [ico-downloads]: https://img.shields.io/packagist/dt/eduardoarandah/autohttptests.svg?style=flat-square 133 | 134 | [link-packagist]: https://packagist.org/packages/eduardoarandah/autohttptests 135 | [link-travis]: https://travis-ci.org/eduardoarandah/autohttptests 136 | [link-scrutinizer]: https://scrutinizer-ci.com/g/eduardoarandah/autohttptests/code-structure 137 | [link-code-quality]: https://scrutinizer-ci.com/g/eduardoarandah/autohttptests 138 | [link-downloads]: https://packagist.org/packages/eduardoarandah/autohttptests 139 | [link-author]: https://github.com/eduardoarandah 140 | [link-contributors]: ../../contributors 141 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eduardoarandah/autohttptests", 3 | "type": "library", 4 | "description": "Autogenerate http tests for laravel", 5 | "keywords": ["eduardoarandah", "autohttptests"], 6 | "homepage": "https://github.com/eduardoarandah/autohttptests", 7 | "license": "MIT", 8 | "authors": [ 9 | { 10 | "name": "Eduardo Aranda Hernandez", 11 | "email": "eduardoarandah@gmail.com", 12 | "homepage": "https://eduardoarandah.github.io/", 13 | "role": "Developer" 14 | } 15 | ], 16 | "require": { 17 | "illuminate/support": "^7.0|^8.0", 18 | "php": "^7.1|^8.0" 19 | }, 20 | "require-dev": { 21 | "phpunit/phpunit": ">=5.4.3" 22 | }, 23 | "autoload": { 24 | "psr-4": { 25 | "EduardoArandaH\\AutoHttpTests\\": "src" 26 | } 27 | }, 28 | "autoload-dev": { 29 | "psr-4": { 30 | "EduardoArandaH\\AutoHttpTests\\": "tests" 31 | } 32 | }, 33 | "scripts": { 34 | "test": "phpunit", 35 | "check-style": "phpcs -p --standard=PSR2 --runtime-set ignore_errors_on_exit 1 --runtime-set ignore_warnings_on_exit 1 src tests", 36 | "fix-style": "phpcbf -p --standard=PSR2 --runtime-set ignore_errors_on_exit 1 --runtime-set ignore_warnings_on_exit 1 src tests" 37 | }, 38 | "extra": { 39 | "laravel": { 40 | "providers": [ 41 | "EduardoArandaH\\AutoHttpTests\\AutoHttpTestsServiceProvider" 42 | ] 43 | }, 44 | "branch-alias": { 45 | "dev-master": "1.0-dev" 46 | } 47 | }, 48 | "config": { 49 | "sort-packages": true 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/AutoHttpTestsServiceProvider.php: -------------------------------------------------------------------------------- 1 | app[Kernel::class]; 19 | $kernel->pushMiddleware(\EduardoArandaH\AutoHttpTests\app\Http\Middleware\AutoHttpTests::class); 20 | 21 | //register command 22 | if ($this->app->runningInConsole()) { 23 | $this->commands([ 24 | \EduardoArandaH\AutoHttpTests\app\Console\Commands\AutoHttpTest::class 25 | ]); 26 | } 27 | } 28 | 29 | /** 30 | * Register any package services. 31 | * 32 | * @return void 33 | */ 34 | public function register() 35 | { 36 | // 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/TestGenerator.php: -------------------------------------------------------------------------------- 1 | isUrlBlackListed($request)) { 10 | return ""; 11 | } 12 | $actions = []; 13 | $actions[] = $this->getActingAs($request); 14 | $actions[] = $this->getMethod($request); 15 | $actions[] = $this->getStatusCode($response); 16 | $actions[] = $this->getErrors($request, $response); 17 | 18 | $out = "\n\t\t\$this" . implode("", $actions) . ";"; 19 | 20 | return $out; 21 | } 22 | public function representArrayKV($array) 23 | { 24 | $out = "["; 25 | foreach ($array as $key => $value) { 26 | if (!in_array($key, ["_token", "_method"])) { 27 | if (!is_array($key) && !is_array($value)) { 28 | $out .= "\n\t\t\"$key\" => \"$value\","; 29 | } 30 | } 31 | } 32 | $out .= "\n\t\t]"; 33 | return $out; 34 | } 35 | public function representArray($array) 36 | { 37 | $out = "["; 38 | foreach ($array as $key) { 39 | $out .= "\n\t\t\"$key\","; 40 | } 41 | $out .= "\n\t\t]"; 42 | return $out; 43 | } 44 | public function shortenUrl($url) 45 | { 46 | return str_replace(config("app.url"), "", $url); 47 | } 48 | public function isUrlBlackListed($request) 49 | { 50 | $shortUrl = $this->shortenUrl($request->getUri()); 51 | $urlBlacklist = ["_debugbar"]; 52 | 53 | //urls blacklist 54 | foreach ($urlBlacklist as $urlIgnored) { 55 | if (str_contains($shortUrl, $urlIgnored)) { 56 | return true; 57 | } 58 | } 59 | return false; 60 | } 61 | public function getActingAs($request) 62 | { 63 | //acting as user 64 | if ($request->user()) { 65 | $user = $request->user(); 66 | $class = get_class($user); 67 | return "\n\t\t->actingAs(\\" . 68 | $class . 69 | "::find(" . 70 | $user->id . 71 | "))"; 72 | } else { 73 | return ""; 74 | } 75 | } 76 | public function getMethod($request) 77 | { 78 | $shortUrl = $this->shortenUrl($request->getUri()); 79 | //make request 80 | $method = strtolower($request->getMethod()); 81 | if (count($request->all())) { 82 | $requestParams = $this->representArrayKV($request->all()); 83 | return "\n\t\t->$method(\"$shortUrl\",$requestParams)"; 84 | } else { 85 | return "\n\t\t->$method(\"$shortUrl\")"; 86 | } 87 | } 88 | public function getStatusCode($response) 89 | { 90 | //Status 91 | $status = $response->getStatusCode(); 92 | return "\n\t\t->assertStatus($status)"; 93 | } 94 | public function getErrors($request, $response) 95 | { 96 | //Has errors? 97 | if ($request->getSession() && $request->getSession()->has("errors")) { 98 | foreach ( 99 | $request 100 | ->getSession() 101 | ->get("errors") 102 | ->getBags() 103 | as $errorBag 104 | ) { 105 | $fieldsWithError = $this->representArray($errorBag->keys()); 106 | return "\n\t\t->assertSessionHasErrors($fieldsWithError)"; 107 | } 108 | } else { 109 | //Redirect 110 | if ($response->isRedirect()) { 111 | $redirectUrl = $this->shortenUrl( 112 | $response->headers->get("Location") 113 | ); 114 | return "\n\t\t->assertRedirect(\"$redirectUrl\")"; 115 | } 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/app/Console/Commands/AutoHttpTest.php: -------------------------------------------------------------------------------- 1 | argument("name")) { 22 | $name = $this->argument("name"); 23 | } else { 24 | $name = $this->ask("Name your test example: HomePage"); 25 | } 26 | 27 | $name = ucfirst($name); 28 | 29 | //determine final path 30 | $finalPath = base_path("tests/Feature/{$name}Test.php"); 31 | 32 | //test already exist? 33 | if (file_exists($finalPath)) { 34 | $this->line("Sorry, that test already exists in $finalPath"); 35 | return; 36 | } 37 | 38 | //start template 39 | file_put_contents($filePath, $this->startTemplate($name)); 40 | 41 | $this->line("Recording..."); 42 | 43 | //finish and save 44 | if ($this->confirm("Save recording?") && file_exists($filePath)) { 45 | //finish template 46 | file_put_contents( 47 | $filePath, 48 | $this->finishTemplate(), 49 | FILE_APPEND | LOCK_EX 50 | ); 51 | 52 | //move to its final destination 53 | if (!file_exists(base_path("tests/Feature"))) { 54 | mkdir(base_path("tests/Feature")); 55 | } 56 | rename($filePath, $finalPath); 57 | 58 | //tell the user 59 | $this->line("Test saved as $finalPath"); 60 | return; 61 | } else { 62 | //if cancelled, delete 63 | unlink($filePath); 64 | } 65 | } 66 | public function startTemplate($name) 67 | { 68 | return "isCommandRunning()) { 32 | return; 33 | } 34 | 35 | //create generator 36 | $testGenerator = new TestGenerator(); 37 | $test = $testGenerator->generate($request, $response); 38 | 39 | if ($test) { 40 | $filePath = storage_path("autohttptests.txt"); 41 | file_put_contents($filePath, $test . "\n", FILE_APPEND | LOCK_EX); 42 | } 43 | } 44 | public function isCommandRunning() 45 | { 46 | // TODO: launch the command and check if it's running 47 | // (new \EduardoArandaH\AutoHttpTests\app\Http\Middleware\AutoHttpTests())->isCommandRunning(); 48 | 49 | // check processes 50 | $process = new Process(["ps", "ax"]); 51 | $process->run(); 52 | 53 | // if failed, return 54 | if (!$process->isSuccessful()) { 55 | return false; 56 | } 57 | 58 | // check if command is running 59 | $output = $process->getOutput(); 60 | return str_contains($output, "autohttptest:create"); 61 | } 62 | } 63 | --------------------------------------------------------------------------------