├── .github └── workflows │ ├── style.yml │ └── tests.yml ├── .gitignore ├── .php_cs.dist ├── LICENSE ├── README.md ├── composer.json ├── composer.lock ├── docker-compose.yml ├── docker └── testing │ └── dockerfile ├── phpunit.xml.dist ├── publishable ├── config │ └── route-coverage.php └── tests │ └── Feature │ └── zRouteCoverageTest.php ├── src ├── Helpers │ └── RouteHelper.php ├── Http │ └── Middleware │ │ └── CollectCodeCoverage.php ├── Providers │ └── CoverageServiceProvider.php └── helpers.php ├── test_coverage ├── .gitignore ├── cache │ └── .gitignore └── html │ └── .gitignore └── tests ├── Feature ├── ItCanBeInstalledTest.php ├── ItCollectsTestedRoutesTest.php └── ItHasTheExpectedTestingCriteriaTest.php ├── TestCase.php └── Unit └── .gitkeep /.github/workflows/style.yml: -------------------------------------------------------------------------------- 1 | name: Style 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | fix-style: 13 | name: PHP-CS-Fixer Enforce Code Style 14 | timeout-minutes: 15 15 | runs-on: ubuntu-latest 16 | env: 17 | COMPOSER_NO_INTERACTION: 1 18 | 19 | steps: 20 | - name: Checkout code 21 | uses: actions/checkout@v2 22 | 23 | - name: Setup PHP 24 | uses: shivammathur/setup-php@v2 25 | with: 26 | php-version: 8.0 27 | coverage: none 28 | tools: composer:v2 29 | 30 | - name: Install dependencies 31 | run: composer update --prefer-dist --no-suggest --no-progress 32 | 33 | - name: Run PHP-CS-Fixer 34 | run: composer fix-style 35 | continue-on-error: true 36 | 37 | - name: Don't Commit composer.json change 38 | run: git checkout -- composer.json 39 | 40 | - name: Don't Commit composer.lock change 41 | run: git checkout -- composer.lock 42 | 43 | - name: Commit any changes back to the repo 44 | uses: stefanzweifel/git-auto-commit-action@v4 45 | with: 46 | commit_message: "[GitHub Action: Style] composer fix-style" 47 | commit_author: github-actions 48 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | php-tests: 13 | runs-on: ubuntu-latest 14 | timeout-minutes: 15 15 | env: 16 | COMPOSER_NO_INTERACTION: 1 17 | 18 | strategy: 19 | matrix: 20 | php: [8.0, 7.4] 21 | dependency-version: [prefer-lowest, prefer-stable] 22 | phpunit: [9.5.2] 23 | 24 | name: PHPUnit-${{ matrix.phpunit }} -- PHP-${{ matrix.php }} -- ${{ matrix.dependency-version }} 25 | 26 | steps: 27 | - name: Checkout code 28 | uses: actions/checkout@v2 29 | 30 | - name: Install dependencies 31 | uses: php-actions/composer@v5 32 | with: 33 | php_version: ${{ matrix.php }} 34 | 35 | - name: PHPUnit Tests 36 | uses: php-actions/phpunit@v2 37 | with: 38 | version: ${{ matrix.phpunit }} 39 | php_version: ${{ matrix.php }} 40 | php_extensions: xdebug 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | vendor 2 | .phpunit.result.cache 3 | ray.php 4 | -------------------------------------------------------------------------------- /.php_cs.dist: -------------------------------------------------------------------------------- 1 | in(__DIR__.'/src') 5 | ->in(__DIR__.'/tests') 6 | ->in(__DIR__.'/publishable'); 7 | 8 | return (new PhpCsFixer\Config()) 9 | ->setRules([ 10 | '@Symfony' => true, 11 | 'no_superfluous_phpdoc_tags' => true, 12 | 'php_unit_method_casing' => [ 13 | 'case' => 'snake_case', 14 | ], 15 | 'yoda_style' => [ 16 | 'equal' => false, 17 | 'identical' => false, 18 | 'less_and_greater' => false, 19 | ], 20 | 'binary_operator_spaces' => [ 21 | 'operators' => [ 22 | '=>' => 'align_single_space_minimal', 23 | '=' => 'align_single_space_minimal', 24 | ], 25 | ], 26 | ]) 27 | ->setLineEnding("\n") 28 | ->setUsingCache(false) 29 | ->setRiskyAllowed(true) 30 | ->setFinder($finder); 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Jeff Madsen 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 | # Laravel Route Coverage 2 | 3 | [![Tests](https://github.com/jrmadsen67/laravel-route-coverage-test/actions/workflows/tests.yml/badge.svg)](https://github.com/jrmadsen67/laravel-route-coverage-test/actions/workflows/tests.yml) 4 | [![Style](https://github.com/jrmadsen67/laravel-route-coverage-test/actions/workflows/style.yml/badge.svg)](https://github.com/jrmadsen67/laravel-route-coverage-test/actions/workflows/style.yml) 5 | 6 | Tests to ensure that all routes are covered by atleast one feature test. 7 | 8 | This is a slightly crude but effective way of checking which of your routes lack feature tests. It simply uses middleware to record all routes being hit during the feature tests, checks to ensure that matches the full route amount. 9 | 10 | The `zRouteCoverageTest` has a funny name for a reason - PHPUnit runs tests in alphabetical order. To most easily capture all the test coverage data _first_, keep it named to run last, and in your Feature directory. Also - the output relies on routes having names to give useful data. Happy Testing! 11 | 12 | --- 13 | 14 | ## Index 15 | 16 | - [Installation](#installation) 17 | - [Usage](#usage) 18 | - [Testing](#testing) 19 | - [Changelog](#changelog) 20 | 21 | --- 22 | 23 | # Installation 24 | 25 | Via Composer, you can run a `composer require` which will grab the latest version of this repo... 26 | 27 | ```sh 28 | composer require --dev jrmadsen67/laravel-route-coverage-test 29 | ``` 30 | 31 | ...and then... 32 | 33 | ```sh 34 | php artisan vendor:publish --provider="jrmadsen67\LaravelRouteCoverageTest\Providers\CoverageServiceProvider" 35 | ``` 36 | 37 | ...to publish the required config and feature test file into your app. The middleware is automatically applied globally by this packages service provider. 38 | 39 | **Note:** See version tagged `1.1` for Laravel `<5.5` support. 40 | 41 | --- 42 | 43 | ## Installation - This Package Version vs. PHP & Laravel Versions 44 | 45 | The following table describes which version of this packagae you will require for the given PHP & Laravel version. 46 | 47 | | Package Version | PHP Version | Laravel Version | 48 | | --------------- | ------------ | --------------- | 49 | | ^2.0 | ^7.4 \| ^8.0 | ^7.0 \| ^8.0 | 50 | | ^1.0 | - | - | 51 | 52 | --- 53 | 54 | # Usage 55 | 56 | Run your tests as normal via phpunit, or the default Laravel test suite: 57 | 58 | ```sh 59 | php artisan test 60 | 61 | # OR 62 | 63 | vendor/bin/phpunit 64 | ``` 65 | 66 | --- 67 | 68 | ## Testing 69 | 70 | There is a Docker container that is pre-built that contains an Alpine CLI release of PHP + PHPUnit + xdebug. This is setup to test the project and can be setup via the following: 71 | 72 | ```sh 73 | composer build 74 | ``` 75 | 76 | This should trigger Docker Compose to build the image. 77 | 78 | There are tests for all code written, in which can be run via: 79 | 80 | ```sh 81 | # Using PHPUnit, with code coverage reporting, within the Docker container 82 | composer test 83 | 84 | # Using PHPUnit, with code coverage reporting, within the Docker container, specifying a direct test 85 | composer test-filtered ItGeneratesSqlFromMigrations 86 | 87 | # Using PHPUnit, with code coverage reporting, using local phpunit and xdebug 88 | composer test-local 89 | 90 | # Using PHPUnit, with code coverage reporting, using local phpunit and xdebug, specifying a direct test 91 | composer test-local-filtered ItGeneratesSqlFromMigrations 92 | ``` 93 | 94 | In those tests, there are Feature tests for a production ready implementation of the package. There are also Unit tests for each class written for full coverage. 95 | 96 | You can also easily open a shell in the testing container by using the command: 97 | 98 | ```sh 99 | composer shell 100 | ``` 101 | 102 | --- 103 | 104 | ## Changelog 105 | 106 | Any and all project changes for releases should be documented below. Versioning follows the [SEMVER](https://semver.org/) standard. 107 | 108 | --- 109 | 110 | ### Version 2.0.0 111 | 112 | Big project refactor, see the changelog sections for more info. 113 | 114 | #### Added 115 | 116 | - Config support for defining excluded routes and route groups 117 | - Docker container for a fixed testing environment with code coverage support via XDebug 118 | - New global tests path helper `tests_path()` that will generate a fully qualified path to the tests directory 119 | - Various composer scripts to shortcut common testing and style fixing functionality 120 | - GitHub actions for automated testing and style fixes as a basic CI pipeline 121 | - Spatie Ray support for enhanced debugging 122 | - Testing of this packages functionality 123 | - Code coverage and getting the coverage to around ~95% 124 | - PHP-CS-Fixer for making the project style adhere to a fixed set of code standards 125 | - Binding the package to both PHP and Laravel version requirements for easy compatibility reference 126 | 127 | #### Changed 128 | 129 | - Refactored shared methods between the middleware and test into a new `ReportHelper` object for testing purposes and centralising of reusable code 130 | - Refactored the package middleware to reside instead within the package and its namespace, again so it can be tested and also to not pollute the installed application 131 | - Renamed `xCoverageTest` to `zRouteCoverageTest` to better reflect what the test covers 132 | - Complete refactor of the readme to best reflect the changes in this update 133 | 134 | #### Fixed 135 | 136 | - The middleware now applies itself globally using the latest method of doing so for Laravel versions `>5.5` 137 | - Manual version lock in for `spatie/macroable` to `^1.0` to fix composer getting confused about requirement conflicts 138 | 139 | #### Removed 140 | 141 | - The package middleware is no longer published into the installed application and is instead held within the package and its namespace 142 | 143 | --- 144 | 145 | ### Version 1.1 146 | 147 | Initial release. Pre-semver implementation. 148 | 149 | #### Added 150 | 151 | - Everything 152 | 153 | #### Changed 154 | 155 | - Everything 156 | 157 | #### Fixed 158 | 159 | - Everything 160 | 161 | #### Removed 162 | 163 | - Everything 164 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jrmadsen67/laravel-route-coverage-test", 3 | "description": "Route coverage test for Laravel.", 4 | "version": "2.0.0", 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Adam Albright", 9 | "email": "adam@cactuslabs.net", 10 | "homepage": "http://cactuslabs.net" 11 | }, 12 | { 13 | "name": "Jeff Madsen", 14 | "email": "jrmadsen67@gmail.com", 15 | "homepage": "http://codebyjeff.com" 16 | }, 17 | { 18 | "name": "Johnny Mast", 19 | "email": "mastjohnny@gmail.com", 20 | "homepage": "https://johnnymast.io" 21 | }, 22 | { 23 | "name": "Ben T", 24 | "email": "git@othyn.com", 25 | "homepage": "https://othyn.com" 26 | } 27 | ], 28 | "require": { 29 | "php": "^7.4|^8.0", 30 | "illuminate/support": "^7.0|^8.0", 31 | "spatie/macroable": "^1.0" 32 | }, 33 | "require-dev": { 34 | "orchestra/testbench": "^6.0", 35 | "friendsofphp/php-cs-fixer": "^2.18", 36 | "phpunit/phpunit": "^9.5", 37 | "spatie/ray": "^1.22" 38 | }, 39 | "autoload": { 40 | "psr-4": { 41 | "jrmadsen67\\LaravelRouteCoverageTest\\": "src/" 42 | }, 43 | "files": [ 44 | "src/helpers.php" 45 | ] 46 | }, 47 | "autoload-dev": { 48 | "psr-4": { 49 | "Tests\\": "tests" 50 | } 51 | }, 52 | "extra": { 53 | "laravel": { 54 | "providers": [ 55 | "jrmadsen67\\LaravelRouteCoverageTest\\Providers\\CoverageServiceProvider" 56 | ] 57 | } 58 | }, 59 | "scripts": { 60 | "build": "docker-compose build", 61 | "shell": "docker-compose run --rm testing /bin/sh", 62 | "fix-style": "vendor/bin/php-cs-fixer fix", 63 | "fix-misc": "composer dump-autoload", 64 | "test": "docker-compose run --rm testing vendor/bin/phpunit", 65 | "test-filtered": "docker-compose run --rm testing vendor/bin/phpunit --filter", 66 | "test-local": "XDEBUG_MODE=coverage vendor/bin/phpunit", 67 | "test-local-filtered": "XDEBUG_MODE=coverage vendor/bin/phpunit --filter" 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.7" 2 | 3 | services: 4 | testing: 5 | container_name: testing 6 | build: 7 | context: . 8 | dockerfile: ./docker/testing/dockerfile 9 | restart: "no" 10 | volumes: 11 | - ./:/testing 12 | -------------------------------------------------------------------------------- /docker/testing/dockerfile: -------------------------------------------------------------------------------- 1 | FROM php:8.0-cli-alpine 2 | 3 | RUN docker-php-ext-install pdo pdo_mysql 4 | 5 | RUN apk add --no-cache $PHPIZE_DEPS \ 6 | && pecl install xdebug-3.0.2 \ 7 | && docker-php-ext-enable xdebug \ 8 | && apk del $PHPIZE_DEPS \ 9 | && echo 'xdebug.mode=coverage' >> /usr/local/etc/php/php.ini 10 | 11 | RUN curl -fsSL https://getcomposer.org/installer | php \ 12 | && mv composer.phar /usr/local/bin/composer \ 13 | && composer global require phpunit/phpunit ^9.3 --no-progress --no-scripts --no-interaction 14 | 15 | ENV PATH /root/.composer/vendor/bin:$PATH 16 | 17 | WORKDIR /testing 18 | 19 | CMD ["phpunit"] 20 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | ./src 7 | 8 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | ./tests/Unit 24 | 25 | 26 | 27 | 28 | ./tests/Feature 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /publishable/config/route-coverage.php: -------------------------------------------------------------------------------- 1 | [ 15 | // E.g. ... 16 | // '/my/url', 17 | // 'Closure /my/url', 18 | ], 19 | 20 | /* 21 | |--------------------------------------------------------------------------- 22 | | Excluded Route Groups 23 | |--------------------------------------------------------------------------- 24 | | 25 | | Place any route group names in here to ignore all routes of a given group. 26 | | For example, in order to ignore all passport route names, as they are all 27 | | prefixed with 'passport.*', place 'passport' into this array. 28 | | 29 | */ 30 | 'exclude_route_groups' => [ 31 | // E.g. ... 32 | // 'debugbar', 33 | // 'dusk', 34 | // 'passport', 35 | ], 36 | 37 | /* 38 | |--------------------------------------------------------------------------- 39 | | Route Groups Seperator 40 | |--------------------------------------------------------------------------- 41 | | 42 | | The seperator that is to be used when building route group names to be 43 | | excluded, this defaults to the Laravel default character, period. 44 | | 45 | */ 46 | 'route_groups_seperator' => '.', 47 | ]; 48 | -------------------------------------------------------------------------------- /publishable/tests/Feature/zRouteCoverageTest.php: -------------------------------------------------------------------------------- 1 | assertSame(0, count($routesUntested), "Missing feature tests for Routes:\n".implode("\n", $routesUntested)); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Helpers/RouteHelper.php: -------------------------------------------------------------------------------- 1 | getName())) { 22 | return $route->getName() ?? 'unknown'; 23 | } 24 | 25 | if ($route->getActionName() && $route->getActionName() !== 'Closure') { 26 | return $route->getActionName(); 27 | } 28 | 29 | return $route->getActionName().' /'.$route->uri(); 30 | } 31 | 32 | /** 33 | * Determine if the route name provided is to be excluded from the test coverage. 34 | */ 35 | public static function isExcludedRoute(string $routeName): bool 36 | { 37 | // Has to be this way due to PHP's pass by ref array_walk requirements 38 | $excludedRoutes = config('route-coverage.exclude_routes', []); 39 | $excludedRouteGroups = config('route-coverage.exclude_route_groups', []); 40 | 41 | $isExcluded = false; 42 | 43 | array_walk( 44 | $excludedRoutes, 45 | function ($excludeName) use ($routeName, &$isExcluded) { 46 | if ($isExcluded = Str::is($excludeName, $routeName)) { 47 | return; 48 | } 49 | } 50 | ); 51 | 52 | if ($isExcluded) { 53 | return $isExcluded; 54 | } 55 | 56 | array_walk( 57 | $excludedRouteGroups, 58 | function ($excludeGroupName) use ($routeName, &$isExcluded) { 59 | $isExcluded = $isExcluded ? $isExcluded : Str::startsWith($routeName, $excludeGroupName.config('route_groups_seperator', '.')); 60 | if ($isExcluded) { 61 | return; 62 | } 63 | } 64 | ); 65 | 66 | return $isExcluded; 67 | } 68 | 69 | /** 70 | * Gathers all the untested routes into a formatted array that can itself be tested and also consumed by the test suite. 71 | */ 72 | public static function getUntestedRoutes(): array 73 | { 74 | $routesTested = collect(CollectCodeCoverage::$routesTested)->groupBy('name'); 75 | $routesUntested = []; 76 | 77 | foreach (Route::getRoutes() as $route) { 78 | $routeName = RouteHelper::getRouteName($route); 79 | $hits = $routesTested->get($routeName); 80 | 81 | if (self::isExcludedRoute($routeName)) { 82 | continue; 83 | } 84 | 85 | foreach ($route->methods() as $method) { 86 | if ($method === 'HEAD') { 87 | continue; 88 | } elseif ($hits === null || $hits->where('method', '=', $method)->count() === 0) { 89 | $routesUntested[] = $routeName.' '.$method; 90 | } 91 | } 92 | } 93 | 94 | asort($routesUntested); 95 | 96 | return $routesUntested; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/Http/Middleware/CollectCodeCoverage.php: -------------------------------------------------------------------------------- 1 | runningUnitTests()) { 30 | static::$routesTested[] = [ 31 | 'url' => $request->getRequestUri(), 32 | 'name' => RouteHelper::getRouteName(Route::getCurrentRoute()), 33 | 'method' => $request->getMethod(), 34 | ]; 35 | } 36 | 37 | return $response; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Providers/CoverageServiceProvider.php: -------------------------------------------------------------------------------- 1 | 20 | * @license https://opensource.org/licenses/MIT MIT 21 | * 22 | * @see https://github.com/jrmadsen67/laravel-route-coverage-test 23 | * @since GIT:1.1 24 | */ 25 | class CoverageServiceProvider extends ServiceProvider 26 | { 27 | /** 28 | * Helper for the base directory for all the supplimentary package stuff. 29 | */ 30 | const PACKAGE_RESOURCE_DIR = __DIR__.'/../../publishable'; 31 | 32 | /** 33 | * Tell Laravel where to look for the package it's migrations. 34 | * 35 | * @return void 36 | */ 37 | public function boot(Router $router) 38 | { 39 | // Register the middleware globally 40 | $kernel = $this->app->make(Kernel::class); 41 | $kernel->pushMiddleware(CollectCodeCoverage::class); 42 | 43 | // Publishing is only necessary when using the CLI 44 | if ($this->app->runningInConsole()) { 45 | $this->bootForConsole(); 46 | } 47 | } 48 | 49 | /** 50 | * Console-specific booting. 51 | * 52 | * @return void 53 | */ 54 | protected function bootForConsole() 55 | { 56 | // Publishing the configuration file. 57 | $this->publishes([ 58 | self::PACKAGE_RESOURCE_DIR.'/config/route-coverage.php' => config_path('route-coverage.php'), 59 | ], 'config'); 60 | 61 | // Publishing the tests. 62 | $this->publishes([ 63 | self::PACKAGE_RESOURCE_DIR.'/tests/Feature/zRouteCoverageTest.php' => tests_path('Feature/zRouteCoverageTest.php'), 64 | ], 'tests'); 65 | } 66 | 67 | /** 68 | * Register publishable files. 69 | * 70 | * @return void 71 | */ 72 | public function register() 73 | { 74 | $this->mergeConfigFrom( 75 | self::PACKAGE_RESOURCE_DIR.'/config/route-coverage.php', 76 | 'route-coverage' 77 | ); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/helpers.php: -------------------------------------------------------------------------------- 1 | basePath('tests'.($path ? DIRECTORY_SEPARATOR.$path : $path)); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /test_coverage/.gitignore: -------------------------------------------------------------------------------- 1 | report.xml 2 | report.txt 3 | -------------------------------------------------------------------------------- /test_coverage/cache/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /test_coverage/html/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /tests/Feature/ItCanBeInstalledTest.php: -------------------------------------------------------------------------------- 1 | refreshApplication(); 16 | 17 | return [ 18 | 'tests files' => [ 19 | 'tests', 20 | tests_path('Feature/zRouteCoverageTest.php'), 21 | ], 22 | 'config files' => [ 23 | 'config', 24 | config_path('route-coverage.php'), 25 | ], 26 | ]; 27 | } 28 | 29 | /** 30 | * @dataProvider providesPublishableFiles 31 | */ 32 | public function test_the_package_service_provider_can_publish(string $type, string $path): void 33 | { 34 | // Make sure we're starting from a clean state 35 | if (File::exists($path)) { 36 | File::delete($path); 37 | } 38 | 39 | $this->assertFileDoesNotExist($path); 40 | 41 | // Given the user wants to publish the package data 42 | // When the publish command is run for a given tag (or all tags if omitted) 43 | Artisan::call('vendor:publish --provider="jrmadsen67\\\LaravelRouteCoverageTest\\\Providers\\\CoverageServiceProvider" --tag="'.$type.'"'); 44 | 45 | // Then verify that the published item exists at the expected location 46 | $this->assertStringNotContainsStringIgnoringCase( 47 | 'Unable to locate publishable resources.', 48 | Artisan::output(), 49 | "Package {$type} publish failed." 50 | ); 51 | 52 | $this->assertFileExists($path); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /tests/Feature/ItCollectsTestedRoutesTest.php: -------------------------------------------------------------------------------- 1 | setupTestingRoutes(); 24 | 25 | // When a route is called 26 | $this->get('_named_test/one'); 27 | 28 | // Then the route should be collected into the tested routes array 29 | $this->assertContains( 30 | [ 31 | 'url' => '/_named_test/one', 32 | 'name' => 'testing.one', 33 | 'method' => 'GET', 34 | ], 35 | CollectCodeCoverage::$routesTested 36 | ); 37 | } 38 | 39 | public function test_the_middleware_collects_closue_tested_routes(): void 40 | { 41 | // Given some routes 42 | $this->setupTestingRoutes(); 43 | 44 | // When a route is called 45 | $this->get('_test/three'); 46 | 47 | // Then the route should be collected into the tested routes array 48 | $this->assertContains( 49 | [ 50 | 'url' => '/_test/three', 51 | 'name' => 'Closure /_test/three', 52 | 'method' => 'GET', 53 | ], 54 | CollectCodeCoverage::$routesTested 55 | ); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /tests/Feature/ItHasTheExpectedTestingCriteriaTest.php: -------------------------------------------------------------------------------- 1 | setupTestingRoutes(); 26 | 27 | // When a route is called 28 | $this->get('_named_test/one'); 29 | 30 | // When the test suite executes the method to gather the non-hit routes 31 | $routesUntested = RouteHelper::getUntestedRoutes(); 32 | 33 | // Then the hit routes should not be collected into the untested routes array... 34 | $this->assertNotContains('testing.one GET', $routesUntested); 35 | 36 | // ...and only the non-hit routes should be collected into the untested routes array 37 | $this->assertContains('testing.two GET', $routesUntested); 38 | $this->assertContains('Closure /_test/three GET', $routesUntested); 39 | $this->assertContains('Closure /_test/four GET', $routesUntested); 40 | } 41 | 42 | public function test_it_generates_the_test_criteria_for_not_hit_routes_excluding_individual_named_routes(): void 43 | { 44 | // Given some routes 45 | $this->setupTestingRoutes(); 46 | 47 | // Given the config stating not to collect a route 48 | Config::set('route-coverage.exclude_routes', ['testing.one']); 49 | 50 | // When a route is called 51 | $this->get('_named_test/two'); 52 | 53 | // When the test suite executes the method to gather the non-hit routes 54 | $routesUntested = RouteHelper::getUntestedRoutes(); 55 | 56 | // Then the hit routes and excluded routes should not be collected into the untested routes array... 57 | $this->assertNotContains('testing.one GET', $routesUntested); 58 | $this->assertNotContains('testing.two GET', $routesUntested); 59 | 60 | // ...and only the non-hit and included routes should be collected into the untested routes array 61 | $this->assertContains('Closure /_test/three GET', $routesUntested); 62 | $this->assertContains('Closure /_test/four GET', $routesUntested); 63 | } 64 | 65 | public function test_it_generates_the_test_criteria_for_not_hit_routes_excluding_individual_unnamed_routes(): void 66 | { 67 | // Given some routes 68 | $this->setupTestingRoutes(); 69 | 70 | // Given the config stating not to collect a route 71 | Config::set('route-coverage.exclude_routes', ['Closure /_test/three']); 72 | 73 | // When a route is called 74 | $this->get('_test/four'); 75 | 76 | // When the test suite executes the method to gather the non-hit routes 77 | $routesUntested = RouteHelper::getUntestedRoutes(); 78 | 79 | // Then the hit routes and excluded routes should not be collected into the untested routes array... 80 | $this->assertNotContains('Closure /_test/three GET', $routesUntested); 81 | $this->assertNotContains('Closure /_test/four GET', $routesUntested); 82 | 83 | // ...and only the non-hit and included routes should be collected into the untested routes array 84 | $this->assertContains('testing.one GET', $routesUntested); 85 | $this->assertContains('testing.two GET', $routesUntested); 86 | } 87 | 88 | public function test_it_generates_the_test_criteria_for_not_hit_routes_excluding_route_groups(): void 89 | { 90 | // Given some routes 91 | $this->setupTestingRoutes(); 92 | 93 | // Given the config stating not to collect a route 94 | Config::set('route-coverage.exclude_route_groups', ['testing']); 95 | 96 | // When a route is called 97 | $this->get('_named_test/one'); 98 | 99 | // When the test suite executes the method to gather the non-hit routes 100 | $routesUntested = RouteHelper::getUntestedRoutes(); 101 | 102 | // Then the hit routes and excluded routes should not be collected into the untested routes array... 103 | $this->assertNotContains('testing.one GET', $routesUntested); 104 | $this->assertNotContains('testing.two GET', $routesUntested); 105 | 106 | // ...and only the non-hit and included routes should be collected into the untested routes array 107 | $this->assertContains('Closure /_test/three GET', $routesUntested); 108 | $this->assertContains('Closure /_test/four GET', $routesUntested); 109 | } 110 | 111 | public function test_the_test_will_pass_if_all_routes_are_tested_or_excluded(): void 112 | { 113 | // Given some routes 114 | $this->setupTestingRoutes(); 115 | 116 | // Given the config stating not to collect a route 117 | Config::set('route-coverage.exclude_route_groups', ['testing']); 118 | 119 | // When a route is called 120 | $this->get('_test/three'); 121 | $this->get('_test/four'); 122 | 123 | // When the test suite executes the method to gather the non-hit routes 124 | $routesUntested = RouteHelper::getUntestedRoutes(); 125 | 126 | // Then the mirrored test criteria for the publishable/tests/Feature/zRouteCoverageTest.php should pass 127 | $this->assertSame(0, count($routesUntested), "Missing feature tests for Routes:\n".implode("\n", $routesUntested)); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /tests/TestCase.php: -------------------------------------------------------------------------------- 1 | '_named_test', 28 | 'as' => 'testing.', 29 | ], function () { 30 | Route::name('one') 31 | ->get('one', function () { 32 | return response()->noContent(); 33 | }); 34 | 35 | Route::name('two') 36 | ->get('two', function () { 37 | return response()->noContent(); 38 | }); 39 | }); 40 | 41 | Route::group([ 42 | 'prefix' => '_test', 43 | ], function () { 44 | Route::get('three', function () { 45 | return response()->noContent(); 46 | }); 47 | 48 | Route::get('four', function () { 49 | return response()->noContent(); 50 | }); 51 | }); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /tests/Unit/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jrmadsen67/laravel-route-coverage-test/655cbdecb2879a04442a252a2ca8e9c26332b473/tests/Unit/.gitkeep --------------------------------------------------------------------------------