From 41669eea2c8fdf7809202e93a2b58af109ee398f Mon Sep 17 00:00:00 2001 From: Ben Batschelet Date: Wed, 9 Aug 2023 21:51:13 -0500 Subject: [PATCH 1/9] Update dev support files PHPUnit 10 fixes Initial deprecation work --- .editorconfig | 11 +- .gitattributes | 18 ++- .../ISSUE_TEMPLATE/PULL_REQUEST_TEMPLATE.md | 39 +++++ .github/ISSUE_TEMPLATE/bug_report.yml | 55 +++++++ .github/ISSUE_TEMPLATE/feature_request.yml | 34 ++++ .github/workflows/acceptance.yml | 25 ++- .gitignore | 50 +++--- .php-cs-fixer.dist.php | 32 ++-- CONTRIBUTING.md | 151 ++++++++++++++++++ README.md | 4 +- SECURITY.md | 3 + composer.json | 79 ++++----- dev/GenerateConfig.php | 52 ++++-- dev/phpunit10.neon | 8 + {phpstan => dev}/phpunit8.neon | 2 +- dev/phpunit8.xml | 27 ++++ {phpstan => dev}/phpunit9.neon | 2 +- dev/phpunit9.xml | 27 ++++ phpstan.neon.dist | 3 +- phpunit.xml.dist | 41 ++--- src/API/Assert.php | 24 ++- src/API/AssertInterface.php | 43 +++++ src/API/Attribute.php | 2 + src/API/Base.php | 12 +- src/API/File.php | 2 + src/API/Method.php | 2 + src/API/Value.php | 4 +- src/Constraint/Factory.php | 88 +++++++++- test/AttributeUnitTest.php | 20 +-- test/BaseUnitTest.php | 26 +-- test/FileFunctionalTest.php | 4 +- test/MethodUnitTest.php | 28 ++-- test/ValueFunctionalTest.php | 49 +++--- 33 files changed, 754 insertions(+), 213 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/PULL_REQUEST_TEMPLATE.md create mode 100644 .github/ISSUE_TEMPLATE/bug_report.yml create mode 100644 .github/ISSUE_TEMPLATE/feature_request.yml create mode 100644 CONTRIBUTING.md create mode 100644 SECURITY.md create mode 100644 dev/phpunit10.neon rename {phpstan => dev}/phpunit8.neon (85%) create mode 100644 dev/phpunit8.xml rename {phpstan => dev}/phpunit9.neon (90%) create mode 100644 dev/phpunit9.xml create mode 100644 src/API/AssertInterface.php diff --git a/.editorconfig b/.editorconfig index b882d43..882a417 100644 --- a/.editorconfig +++ b/.editorconfig @@ -6,20 +6,23 @@ trim_trailing_whitespace = true charset = utf-8 indent_style = space -[*.yml] -indent_size = 2 - [*.php] indent_size = 4 -[*.xml] +[*.{xml,xml.dist}] indent_size = 4 +[*.{yml,yaml}] +indent_size = 2 + [*.{neon,neon.dist}] indent_size = 2 [*.json] indent_size = 4 +[*.md] +indent_size = 2 + [Makefile] indent_style = tab diff --git a/.gitattributes b/.gitattributes index d5671e4..1d1f8e4 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2,11 +2,13 @@ # https://www.kernel.org/pub/software/scm/git/docs/gitattributes.html # Ignore all test and miscellaneous support files with "export-ignore". -/.* export-ignore -/dev export-ignore -/docs export-ignore -/captainhook.json export-ignore -/phpstan.neon.dist export-ignore -/phpstan export-ignore -/phpunit.xml export-ignore -/test export-ignore +/.* export-ignore +/dev export-ignore +/docs export-ignore +/captainhook.json export-ignore +/CHANGELOG.md export-ignore +/CODE_OF_CONDUCT.md export-ignore +/CONTRIBUTING.md export-ignore +/phpstan.neon.dist export-ignore +/phpunit.xml.dist export-ignore +/test export-ignore diff --git a/.github/ISSUE_TEMPLATE/PULL_REQUEST_TEMPLATE.md b/.github/ISSUE_TEMPLATE/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..463a472 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,39 @@ + + + + + + +## Description + + + +## Contribution Checklist + +- [ ] The contents of this pull request are my own work and may be distributed under the terms of the project license +- [ ] I have read and agree to the Contributing guidelines and the Code of Conduct +- [ ] All new changes are covered by tests +- [ ] All previous tests and checks are passing +- [ ] I have included documentation about this change +- [ ] The details of this change have been added to the `Unreleased` section of the CHANGELOG.md + +**This pull request includes:** + +- [ ] Breaking changes to existing functionality (major release) +- [ ] New functionality (minor release) +- [ ] Fixes for existing functionality (patch release) +- [ ] Fixes to an earlier major or minor release version diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..3632c06 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,55 @@ +name: Bug Report +description: Report an issue with BeBat\Verify +title: "[Bug]: " +labels: ["bug"] +body: + - type: markdown + attributes: + value: Thank you for taking the time to fill out this bug report! + + - type: textarea + attributes: + label: Description + description: A brief description of the bug + validations: + required: true + + - type: textarea + attributes: + label: Steps to Reproduce + description: What was the process that led to this bug? + value: | + 1. + 2. + 3. + ... + validations: + required: true + + - type: textarea + attributes: + label: Expected Result + description: What did you expect to happen when following the steps to reproduce? + validations: + required: true + + - type: textarea + attributes: + label: Actual Result + description: What actually happened? + validations: + required: true + + - type: input + attributes: + label: " Version" + placeholder: "1.2.3" + validations: + required: true + + - type: input + attributes: + label: PHP Version + placeholder: "7.x.x or 8.x.x" + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..2da761c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,34 @@ +name: Feature Request +description: Propose a new feature for BeBat\Verify +title: "[Feature]: " +labels: ["feature"] +body: + - type: markdown + attributes: + value: Thank you for taking the time to propose a new feature! + + - type: textarea + attributes: + label: Description + description: A brief description of the feature you'd like to propose + validations: + required: true + + - type: textarea + attributes: + label: Example Code + description: | + One or more examples of how you think the feature should work and what + its results would be + render: php + + - type: dropdown + attributes: + label: Release Target + description: | + Would this feature be a major (breaking) change to existing + functionality or can it go into a minor release? + options: + - Unknown + - Minor + - Major diff --git a/.github/workflows/acceptance.yml b/.github/workflows/acceptance.yml index d2b5201..040f0fd 100644 --- a/.github/workflows/acceptance.yml +++ b/.github/workflows/acceptance.yml @@ -21,6 +21,7 @@ jobs: phpunit_version: - 8 - 9 + - 10 versions: - php: "7.2" composer: 1 @@ -32,13 +33,28 @@ jobs: composer: 2 - php: "8.1" composer: 2 + - php: "8.2" + composer: 2 exclude: - versions: php: "7.2" phpunit_version: 9 + - versions: + php: "7.2" + phpunit_version: 10 + - versions: + php: "7.3" + phpunit_version: 10 + - versions: + php: "7.4" + phpunit_version: 10 + - versions: + php: "8.0" + phpunit_version: 10 steps: - uses: actions/checkout@v3 + - name: Setup PHP ${{ matrix.versions.php }} uses: shivammathur/setup-php@v2 with: @@ -48,7 +64,7 @@ jobs: - name: Get Composer Cache Directory id: composer-cache - run: echo "::set-output name=dir::$(composer config cache-files-dir)" + run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT - name: Cache Dependencies uses: actions/cache@v3 @@ -60,11 +76,14 @@ jobs: ${{ runner.os }}-composer- - name: Update PHPUnit Version - run: composer require --no-update --no-interaction --no-progress --prefer-dist --update-with-all-dependencies phpunit/phpunit:^${{ matrix.phpunit_version }}.0 + run: composer require --no-update --no-interaction --no-progress --update-with-all-dependencies phpunit/phpunit:^${{ matrix.phpunit_version }}.0 - name: Install Dependencies run: composer install --no-interaction --no-progress + - name: Chech composer.json Format + run: composer normalize --diff --dry-run --no-interaction --ansi + - name: Check Code Style run: composer style:check @@ -72,7 +91,7 @@ jobs: run: composer test:static -- --no-progress - name: Run Unit & Functional Tests - run: composer test:coverage -- -v + run: composer test:coverage - name: Upload Coverage uses: codecov/codecov-action@v3 diff --git a/.gitignore b/.gitignore index 83e1013..1ad68b5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ -# Created by https://www.toptal.com/developers/gitignore/api/linux,macos,phpunit,composer,intellij+all,visualstudiocode -# Edit at https://www.toptal.com/developers/gitignore?templates=linux,macos,phpunit,composer,intellij+all,visualstudiocode +# Created by https://www.toptal.com/developers/gitignore/api/linux,macos,phpunit,composer,intellij+all,php-cs-fixer,visualstudiocode +# Edit at https://www.toptal.com/developers/gitignore?templates=linux,macos,phpunit,composer,intellij+all,php-cs-fixer,visualstudiocode ### Composer ### composer.phar @@ -73,6 +73,9 @@ atlassian-ide-plugin.xml # Cursive Clojure plugin .idea/replstate.xml +# SonarLint plugin +.idea/sonarlint/ + # Crashlytics plugin (for Android Studio and IntelliJ) com_crashlytics_export_strings.xml crashlytics.properties @@ -86,20 +89,13 @@ fabric.properties .idea/caches/build_file_checksums.ser ### Intellij+all Patch ### -# Ignores the whole .idea folder and all .iml files -# See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360 - -.idea/ - -# Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023 +# Ignore everything but code style settings and run configurations +# that are supposed to be shared within teams. -*.iml -modules.xml -.idea/misc.xml -*.ipr +.idea/* -# Sonarlint plugin -.idea/sonarlint +!.idea/codeStyles +!.idea/runConfigurations ### Linux ### *~ @@ -144,6 +140,20 @@ Network Trash Folder Temporary Items .apdisk +### macOS Patch ### +# iCloud generated files +*.icloud + +### PHP-CS-Fixer ### +# Covers PHP CS Fixer +# Reference: https://cs.symfony.com/ + +# Generated files +.php-cs-fixer.cache + +# Local config See: https://cs.symfony.com/doc/config.html +.php-cs-fixer.php + ### PHPUnit ### # Covers PHPUnit # Reference: https://phpunit.de/ @@ -165,23 +175,21 @@ Temporary Items !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json -*.code-workspace +!.vscode/*.code-snippets # Local History for Visual Studio Code .history/ +# Built Visual Studio Code Extensions +*.vsix + ### VisualStudioCode Patch ### # Ignore all local history of files .history .ionide -# Support for Project snippet scope -!.vscode/*.code-snippets - -# End of https://www.toptal.com/developers/gitignore/api/linux,macos,phpunit,composer,intellij+all,visualstudiocode +# End of https://www.toptal.com/developers/gitignore/api/linux,macos,phpunit,composer,intellij+all,php-cs-fixer,visualstudiocode -/.php-cs-fixer.cache -/.php-cs-fixer.php /coverage.xml /phpstan.neon /captainhook.config.json diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php index 9ae8a04..cb6ef5c 100644 --- a/.php-cs-fixer.dist.php +++ b/.php-cs-fixer.dist.php @@ -3,9 +3,7 @@ declare(strict_types=1); $finder = PhpCsFixer\Finder::create() - ->in('dev') - ->in('src') - ->in('test'); + ->in(__DIR__); return (new PhpCsFixer\Config()) ->setRules([ @@ -14,55 +12,53 @@ '@PHPUnit84Migration:risky' => true, '@PhpCsFixer' => true, '@PhpCsFixer:risky' => true, - 'align_multiline_comment' => ['comment_type' => 'phpdocs_like'], 'binary_operator_spaces' => [ 'default' => 'align_single_space_minimal', - 'operators' => ['||' => null, '&&' => null, '|' => 'no_space'], + 'operators' => ['||' => 'single_space', '&&' => 'single_space', '|' => 'no_space'], ], 'class_attributes_separation' => [ 'elements' => [ 'const' => 'only_if_meta', - 'property' => 'only_if_meta', 'method' => 'one', + 'property' => 'only_if_meta', 'trait_import' => 'none', ], ], 'class_definition' => [ - 'single_item_single_line' => true, 'multi_line_extends_each_single_line' => true, + 'single_item_single_line' => true, + 'space_before_parenthesis' => true, ], 'concat_space' => ['spacing' => 'one'], - 'control_structure_continuation_position' => true, 'date_time_immutable' => true, - 'declare_parentheses' => true, + 'echo_tag_syntax' => ['format' => 'short'], + 'escape_implicit_backslashes' => ['single_quoted' => true], 'final_public_method_for_abstract_class' => true, - 'general_phpdoc_annotation_remove' => true, + 'general_phpdoc_annotation_remove' => ['annotations' => ['author', 'category', 'filesource', 'source']], 'global_namespace_import' => true, 'mb_str_functions' => true, 'multiline_whitespace_before_semicolons' => ['strategy' => 'no_multi_line'], 'no_extra_blank_lines' => false, - 'no_superfluous_phpdoc_tags' => ['allow_mixed' => true], + 'native_constant_invocation' => true, 'no_unset_on_property' => false, 'nullable_type_declaration_for_default_null_value' => true, 'operator_linebreak' => ['position' => 'beginning', 'only_booleans' => false], 'ordered_class_elements' => ['sort_algorithm' => 'alpha'], - 'ordered_interfaces' => true, 'ordered_imports' => ['imports_order' => ['const', 'class', 'function']], + 'ordered_interfaces' => true, + 'php_unit_test_case_static_method_calls' => ['call_type' => 'self'], 'phpdoc_line_span' => ['const' => 'single', 'property' => 'single'], 'phpdoc_order' => false, // behavior difference between PHPunit 8 & 9 - 'phpdoc_order_by_value' => ['annotations' => ['covers', 'depends', 'group', 'throws']], 'phpdoc_tag_casing' => true, 'phpdoc_types_order' => ['null_adjustment' => 'always_last'], - 'php_unit_test_case_static_method_calls' => ['call_type' => 'this'], 'php_unit_test_class_requires_covers' => false, 'psr_autoloading' => ['dir' => 'src'], 'regular_callable_call' => true, - 'self_static_accessor' => true, - 'simplified_null_return' => true, 'simplified_if_return' => true, - 'single_line_throw' => true, + 'simplified_null_return' => true, 'static_lambda' => true, - 'yoda_style' => false, + 'trailing_comma_in_multiline' => true, + 'yoda_style' => ['equal' => false, 'identical' => false], ]) ->setFinder($finder) ->setRiskyAllowed(true); diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..c68ad30 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,151 @@ + +# Contributing to BeBat/Verify + +Thank you for taking the time to contribute! + +All types of contributions are encouraged and valued. See the [Table of Contents](#table-of-contents) for different ways to help and details about how this project handles them. Please make sure to read the relevant section before making your contribution. It will make things a lot easier for maintainers and improve the experience for everyone. + +> If you like this project but don't have time to contribute directly there are other easy ways to support it and show your appreciation: +> - Star the repository +> - Use `bebat/verify` in your own project +> - Share it on social media +> - Mention this library at local meetups and tell your friends/colleagues + + +## Table of Contents + +- [Code of Conduct](#code-of-conduct) +- [Questions](#questions) +- [Submitting an Issue](#submitting-an-issue) + - [Reporting Bugs](#reporting-bugs) + - [Suggesting Enhancements](#suggesting-enhancements) +- [Contributing Code](#contributing-code) + - [Development Setup](#development-setup) + - [Creating a Pull Request](#creating-a-pull-request) + +## Code of Conduct + +This project and everyone participating in it is governed by the [Contributor Covenant Code of Conduct](CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code. Please report unacceptable behavior to . + +## Questions + +If you have any questions about BeBat/Verify the first, best place to check is the project [documentation](README.md). If your question is not addressed by the existing documentation it may be useful to search for any existing [issues](https://github.com/bbatsche/Verify/issues) related to your question. If you find a suitable issue but still need clarification, you may write your question as a comment to that issue. + +If you feel your question is still not properly address, please do the following: + +1. Open a new [issue](https://github.com/bbatsche/Verify/issues/new). +2. Provide as much context as you can about what you're experiencing. +3. Be sure to include relevant environment details, such as PHP and BeBat/Verify versions. + +## Submitting an Issue + +### Reporting Bugs + + +#### Before Submission + +There are several steps you can do before reporting a bug that will make fixing the issue quicker and easier for maintainers, or could lead to your issue being resolved without creating a bug report at all. + +- Make sure that you are using the latest versions of BeBat/Verify and all of its dependencies. +- Check the [documentation](README.md) to see if your issue is addressed there. +- Search [existing issues](https://github.com/bbatsche/Verify/issues) for your problem. If an issue already exists feel free to give it a `+1` and/or add additional context. +- Collect any relevant information about your issue: + - OS version & platform (Windows, Linux, macOS, x86, ARM, etc) + - Web server & PHP version (nginx, Apache, etc) + - Any relevant input & output + - Especially any exception output & stacktraces + - Can you consistently reproduce the issue? + - Has the behavior changed between versions? + + +> #### Security Issues +> +> You must never report security related issues, vulnerabilities, or bugs including sensitive information to the issue tracker, or elsewhere in public. Instead, sensitive bugs must be sent via email to . See the [Security Policy](SECURITY.md) for more details. + + +#### Creating a Bug Report + +We use GitHub issues to track bugs and errors. If you run into an issue with the project: + +1. Open a [Bug Report Issue](https://github.com/bbatsche/Verify/issues/new?labels=bug&template=bug_report.yml&title=%5BBug%5D%3A+) +2. Fill out all the relevant fields in the form +3. Please provide as much context as possible, this usually includes your code. Ideally you should isolate the problem and create a simplified test case. + +Once your issue is filed the maintainers will do our best to reproduce the problem, determine the appropriate course of action, and resolve the issue in a timely manner. + +### Suggesting Enhancements + +If you have an idea on how to improve BeBat/Verify we are eager to hear it! We welcome any constructive suggestions for improvements to the project and look forward to hearing them. + + +#### Before Submission + +- Make sure that you are using the latest versions of BeBat/Verify and all its dependencies. +- Check the project [documentation](README.md) to see if your idea has already been implemented. +- Search the [existing issues](https://github.com/bbatsche/Verify/issues) to see if the enhancement has already been suggested. If an issue already exists feel free to give it a `+1` and/or add additional context. +- Find out whether your idea fits with the scope and aims of the project. Ideally new features should be useful to a broad set of users and/or streamline something that would otherwise be highly complex. We cannot add features just for their own sake. + + +#### Creating a Feature Request + +1. Open a [Feature Request](https://github.com/bbatsche/Verify/issues/new?labels=feature&template=feature_request.yml&title=%5BFeature%5D%3A+) +2. Fill out the relevant fields on the form + - In the description be sure to include why this feature is important, what use cases will it have, how will it improve people's experiences using the library, etc. + - Include as many examples as you think are relevant. Be sure to consider edge cases and error conditions as well. + - Try to estimate whether this feature will require a major or minor version change to the library. In general, if a feature breaks or replaces some already existing functionality it will require a major change, otherwise it will be added to a minor (or possibly patch) release. + +## Contributing Code + +> ### Notice +> When contributing to this project, you affirm that you have authored 100% of the content, that you have the necessary rights to the content, and that the content you contribute may be provided under the project's [license](LICENSE). + +We are thrilled you would like to contribute code to BeBat/Verify! This is truly one of the best ways to help support our project and the open source community as a whole. Before writing code for the library, we ask that you consider a few things. + +- Is this feature or bug fix needed? Opening a pull request to https://github.com/bbatsche/Verify is no guarantee that it will be accepted and merged. If you are not certain whether your contribution will be useful open a [bug report](#creating-a-bug-report) or [feature request](#creating-a-feature-request) first to discuss it with maintainers. +- Will this change affect or break existing functionality? If there is some functionality being removed it will first need to be deprecated in the next minor release and only then can the new functionality be added, targeting the next major version. +- Will this break compatibility with previous versions of PHP? BeBat/Verify has a long tail of support which is a double edged sword. If your feature is worthwhile it may be added in to the next major release when the minimum version of PHP can be updated. + +### Development Setup + +There is minimal tooling required to contribute to BeBat/Verify. In general the software required to write code for this library is the same as it is to use it, ie: + +- PHP 7.2 or greater +- Git +- Composer +- A coverage driver such as [Xdebug](https://xdebug.org/) or [PCOV](https://github.com/krakjoe/pcov) +- A text editor and terminal of your choice. + +To get started, follow the steps below: + +1. Fork https://github.com/bbatsche/Verify to your own profile +2. Clone your fork locally +3. Create a new branch for your work + - If you are working on a previous version of the library be sure to branch off of the correct version branch. +4. Run `composer install` +5. Begin your work! + +In order to ensure code quality and consistency, BeBat/Verify has various style checks and unit tests bundled with it. These are defined in `composer` scripts. The included commands are: + +- `style:check` — Check that PHP files follow coding standards +- `style:fix` — Automatically fix any coding standard errors +- `test:static` — Run static analysis on code +- `test:phpunit` — Run PHPUnit tests on code +- `test:coverage` — Run PHPUnit tests and generate a coverage report +- `test` — Run `style:check`, `test:static`, and `test:phpunit` in sequence + +BeBat/Verify uses [CaptainHook](http://captainhook.info/) to manage git hooks during development. Whenever you create a commit, CaptainHook will automatically lint your code and run through `composer test` for you. In addition, before pushing your code Captainhook will check the test coverage to ensure it is over 80%. *Do not skip or disable the git hooks unless you have a very good reason for doing so!* + +Finally, there are two additional tools meant to keep dependencies well defined: + +- `vendor/bin/composer-require-checker` — Check that all dependencies are explicitly defined in `composer.json` +- `composer normalize` — Make sure `composer.json` is in a consistent order and format + +If you modify `composer.json` make sure to run `composer normalize` before pushing your code to GitHub. Both these tools will be run against every pull request before merging. + +### Creating a Pull Request + +The final step for whatever coding work you perform is, naturally, opening a pull request. The pull request template will guide you through the required steps and details to get your PR merged easily. Make sure to read the instructions, fill out the required sections, and mark the appropriate items on the checklist. Failure to do so could cause delays in getting your pull request reviewed and merged. + + +## Attribution +This guide is based in part on content from [**contributing-gen**](https://github.com/bttger/contributing-gen). diff --git a/README.md b/README.md index be6dbbe..3ed0217 100644 --- a/README.md +++ b/README.md @@ -7,8 +7,8 @@ With BDD assertions influenced by Chai, Jasmine, and RSpec your assertions will [![Latest Stable Version](https://img.shields.io/packagist/v/bebat/verify.svg?style=flat-square)](https://packagist.org/packages/bebat/verify) [![Required PHP Version](https://img.shields.io/packagist/php-v/bebat/verify.svg?style=flat-square)](https://packagist.org/packages/bebat/verify) -[![License](https://img.shields.io/packagist/l/bebat/verify.svg?style=flat-square)](LICENSE) -[![Build Status](https://img.shields.io/travis/com/bbatsche/Verify.svg?style=flat-square)](https://travis-ci.com/bbatsche/Verify) +[![License](https://img.shields.io/packagist/l/bebat/verify?style=flat-square)](LICENSE) +[![Acceptance Test Status](https://img.shields.io/github/actions/workflow/status/bbatsche/Verify/acceptance.yml?branch=develop&style=flat-square)](https://github.com/bbatsche/Verify/actions/workflows/acceptance.yml) [![Code Coverage](https://img.shields.io/codecov/c/github/bbatsche/Verify?style=flat-square)](https://codecov.io/gh/bbatsche/Verify) Most of the original work was done by [@DavertMik](http://github.com/DavertMik) and [@Ragazzo](http://github.com/Ragazzo) in the [Codeception/Verify](http://github.com/Codeception/Verify) repo. This version provides an alternate API and feature set, while sticking to the original BDD philosophy. diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..42a2586 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,3 @@ +# Security Policy + +Because of their sensitive nature, security issues should **not** be discussed in public forums. If you believe you have found an issue that exposes sensitive information or that could be leveraged in accessing such information, please email it directly to the project maintainer at . We will respond promptly and do our utmost to resolve any security issues in a timely manner. diff --git a/composer.json b/composer.json index b1b9f1d..09704d0 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,7 @@ { "name": "bebat/verify", + "type": "library", "description": "BDD assertion library for PHPUnit", - "license": "MIT", "keywords": [ "bdd", "assertions", @@ -13,6 +13,8 @@ "test", "testing" ], + "homepage": "https://github.com/bbatsche/Verify", + "license": "MIT", "authors": [ { "name": "Michael Bodnarchuk", @@ -23,41 +25,49 @@ "homepage": "https://github.com/bbatsche" } ], - "homepage": "https://github.com/bbatsche/Verify", - "support": { - "issues": "https://github.com/bbatsche/Verify/issues", - "docs": "https://bebatverify.readthedocs.io/en/stable/" - }, "require": { "php": "^7.2 || ^8.0", "ext-ctype": "*", "ext-mbstring": "*", - "bebat/filesystem-assertions": "^1.0", - "phpunit/phpunit": "^8.0 || ^9.0 || ^10.0" - }, - "require-dev": { - "captainhook/captainhook": "^5.10", - "captainhook/plugin-composer": "^5.3", - "codeception/assert-throws": "^1.2", - "ergebnis/composer-normalize": "^2.15", - "friendsofphp/php-cs-fixer": "^3.4", - "maglnet/composer-require-checker": "^2.1 || ^3.8", - "mockery/mockery": "^1.3", - "nette/neon": "^3.0", - "phpstan/extension-installer": "^1.1", - "phpstan/phpstan": "^1.9", - "phpstan/phpstan-deprecation-rules": "^1.0", - "phpstan/phpstan-mockery": "^1.0", - "phpstan/phpstan-phpunit": "^1.1", - "phpstan/phpstan-strict-rules": "^1.1" + "bebat/filesystem-assertions": "^1.1", + "phpunit/phpunit": "^8.0 || ^9.0 || ^10.1" }, "replace": { "codeception/verify": "*" }, + "require-dev": { + "captainhook/captainhook": "~5.14.4", + "captainhook/plugin-composer": "~5.3.3", + "codeception/assert-throws": "~1.3.0", + "ergebnis/composer-normalize": "~2.15.0", + "friendsofphp/php-cs-fixer": "~3.4.0 || ~3.22.0", + "maglnet/composer-require-checker": "^2.1.0 || ~4.4.0", + "mockery/mockery": "~1.3.6 || ~1.6.5", + "nette/neon": "~3.3.3", + "phpstan/extension-installer": "~1.1.0 || ~1.3.1", + "phpstan/phpstan": "~1.10.27", + "phpstan/phpstan-deprecation-rules": "~1.1.4", + "phpstan/phpstan-mockery": "~1.1.1", + "phpstan/phpstan-phpunit": "~1.3.13", + "phpstan/phpstan-strict-rules": "~1.5.1" + }, "suggest": { "codeception/specify": "Highly readable test code blocks for PHPUnit and Codeception" }, - "minimum-stability": "stable", + "config": { + "allow-plugins": { + "captainhook/plugin-composer": true, + "ergebnis/composer-normalize": true, + "ocramius/package-versions": true, + "phpstan/extension-installer": true + }, + "sort-packages": true + }, + "extra": { + "branch-alias": { + "dev-master": "3.2.x-dev" + } + }, "autoload": { "psr-4": { "BeBat\\Verify\\": "src" @@ -73,20 +83,7 @@ "BeBat\\Verify\\Test\\Examples\\": "test/_fixtures/Classes" } }, - "config": { - "allow-plugins": { - "ergebnis/composer-normalize": true, - "phpstan/extension-installer": true, - "ocramius/package-versions": true, - "captainhook/plugin-composer": true - }, - "sort-packages": true - }, - "extra": { - "branch-alias": { - "dev-master": "3.1.x-dev" - } - }, + "minimum-stability": "stable", "scripts": { "post-update-cmd": "BeBat\\Verify\\Dev\\GenerateConfig::generate", "style:check": "php-cs-fixer fix -v --ansi --dry-run --diff", @@ -107,5 +104,9 @@ "test:coverage": "Run unit tests and generate coverage report.", "test:phpunit": "Run unit & functional tests.", "test:static": "Run phpstan static analysis." + }, + "support": { + "issues": "https://github.com/bbatsche/Verify/issues", + "docs": "https://bebatverify.readthedocs.io/en/stable/" } } diff --git a/dev/GenerateConfig.php b/dev/GenerateConfig.php index 251e226..062bf70 100644 --- a/dev/GenerateConfig.php +++ b/dev/GenerateConfig.php @@ -20,13 +20,23 @@ final class GenerateConfig */ public static function generate(): void { - $projectRoot = \dirname(__DIR__); - $phpUnitMajorVersion = explode('.', Version::id())[0]; - $configFile = "{$projectRoot}/phpstan.neon"; - $config = ['includes' => []]; + $projectRoot = \dirname(__DIR__); + $phpUnitVersion = explode('.', Version::id())[0]; + + self::generatePhpstanConfig($projectRoot, $phpUnitVersion); + self::generatePhpUnitConfig($projectRoot, $phpUnitVersion); + } + + /** + * Create phpstan.neon file that includes version specific configurations. + */ + private static function generatePhpstanConfig(string $projectRoot, string $phpUnitVersion): void + { + $configFile = "{$projectRoot}/phpstan.neon"; + $config = ['includes' => []]; if (file_exists($configFile)) { - $config = Neon::decodeFile($configFile); + $config = (array) Neon::decodeFile($configFile); if (!isset($config['includes'])) { $config['includes'] = []; @@ -34,21 +44,43 @@ public static function generate(): void } $config['includes'] = array_filter($config['includes'], static function (string $path): bool { - return mb_strpos($path, '%currentWorkingDirectory%/phpstan') !== 0; + return mb_strpos($path, __DIR__) !== 0; }); if (!\in_array('phpstan.neon.dist', $config['includes'], true)) { $config['includes'][] = 'phpstan.neon.dist'; } - if (file_exists("{$projectRoot}/phpstan/php" . PHP_MAJOR_VERSION . '.neon')) { - $config['includes'][] = '%currentWorkingDirectory%/phpstan/php' . PHP_MAJOR_VERSION . '.neon'; + if (file_exists(__DIR__ . '/php' . \PHP_MAJOR_VERSION . '.neon')) { + $config['includes'][] = __DIR__ . '/php' . \PHP_MAJOR_VERSION . '.neon'; } - if (file_exists("{$projectRoot}/phpstan/phpunit{$phpUnitMajorVersion}.neon")) { - $config['includes'][] = "%currentWorkingDirectory%/phpstan/phpunit{$phpUnitMajorVersion}.neon"; + if (file_exists(__DIR__ . "/phpunit{$phpUnitVersion}.neon")) { + $config['includes'][] = __DIR__ . "/phpunit{$phpUnitVersion}.neon"; } file_put_contents($configFile, Neon::encode($config, true, ' ')); } + + /** + * Symlink phpunit.xml to correct version specific file. + */ + private static function generatePhpUnitConfig(string $projectRoot, string $phpUnitVersion): void + { + $versionConfig = __DIR__ . "/phpunit{$phpUnitVersion}.xml"; + $configPath = $projectRoot . '/phpunit.xml'; + + if (file_exists($configPath)) { + if (!is_link($configPath)) { + // User has their own config, do not modify it + return; + } + + unlink($configPath); + } + + if (file_exists($versionConfig)) { + symlink($versionConfig, $configPath); + } + } } diff --git a/dev/phpunit10.neon b/dev/phpunit10.neon new file mode 100644 index 0000000..74d87db --- /dev/null +++ b/dev/phpunit10.neon @@ -0,0 +1,8 @@ +# Ignorable errors specific to PHPUnit 10 + +parameters: + ignoreErrors: + - '/PHPUnit\\Framework\\Constraint\\ClassHasAttribute/' + - '/PHPUnit\\Framework\\Constraint\\ObjectHasAttribute/' + - '/PHPUnit\\Framework\\Constraint\\ClassHasStaticAttribute/' + - '/Instantiated class PHPUnit\\Framework\\Constraint\\TraversableContains is abstract/' diff --git a/phpstan/phpunit8.neon b/dev/phpunit8.neon similarity index 85% rename from phpstan/phpunit8.neon rename to dev/phpunit8.neon index e18d26a..fe4f6e1 100644 --- a/phpstan/phpunit8.neon +++ b/dev/phpunit8.neon @@ -3,6 +3,6 @@ parameters: ignoreErrors: - '/PHPUnit\\Framework\\Constraint\\StringEqualsStringIgnoringLineEndings/' - - '/PHPUnit\\Framework\\Constraint\\ArrayIsList/' + - '/PHPUnit\\Framework\\Constraint\\IsList/' - '/StringContains constructor invoked with 3 parameters, 1-2 required/' - '/deprecated class PHPUnit\\Framework\\Constraint\\TraversableContains/' diff --git a/dev/phpunit8.xml b/dev/phpunit8.xml new file mode 100644 index 0000000..839a63f --- /dev/null +++ b/dev/phpunit8.xml @@ -0,0 +1,27 @@ + + + + + + ../test + + + + + + ../src + + + + + + + diff --git a/phpstan/phpunit9.neon b/dev/phpunit9.neon similarity index 90% rename from phpstan/phpunit9.neon rename to dev/phpunit9.neon index 3b445b7..c23fefa 100644 --- a/phpstan/phpunit9.neon +++ b/dev/phpunit9.neon @@ -7,6 +7,6 @@ parameters: path: %currentWorkingDirectory%/src/Constraint/Factory.php - '/PHPUnit\\Framework\\Constraint\\StringEqualsStringIgnoringLineEndings/' - - '/PHPUnit\\Framework\\Constraint\\ArrayIsList/' + - '/PHPUnit\\Framework\\Constraint\\IsList/' - '/StringContains constructor invoked with 3 parameters, 1-2 required/' - '/PHPUnit\\Framework\\Constraint\\TraversableContains is abstract/' diff --git a/dev/phpunit9.xml b/dev/phpunit9.xml new file mode 100644 index 0000000..c0aac90 --- /dev/null +++ b/dev/phpunit9.xml @@ -0,0 +1,27 @@ + + + + + + ../test + + + + + + ../src + + + + + + + diff --git a/phpstan.neon.dist b/phpstan.neon.dist index e4a3263..f38ee70 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -1,5 +1,5 @@ parameters: - level: 8 + level: 9 paths: - dev - src @@ -8,7 +8,6 @@ parameters: - test/_fixtures/* checkMissingIterableValueType: true ignoreErrors: - - '/Dynamic call to static method PHPUnit\\Framework\\Assert::/' - '/Call to function is_object\(\) with object will always evaluate to true/' - message: '/Call to an undefined method BeBat\\Verify\\API\\Base::equalTo/' diff --git a/phpunit.xml.dist b/phpunit.xml.dist index fec3c02..90d9303 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,37 +1,24 @@ + xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.3/phpunit.xsd" + bootstrap="vendor/autoload.php" + colors="true" + testdox="true" + beStrictAboutOutputDuringTests="true" + beStrictAboutChangesToGlobalState="true" + failOnWarning="true" + failOnDeprecation="true" + failOnNotice="true"> + test - + + - src + src - - src/Assert.php - - - - - src - - src/Assert.php - - - - - - + diff --git a/src/API/Assert.php b/src/API/Assert.php index 3764822..346b379 100644 --- a/src/API/Assert.php +++ b/src/API/Assert.php @@ -5,12 +5,32 @@ namespace BeBat\Verify\API; use PHPUnit\Framework\Assert as PHPUnitAssert; +use PHPUnit\Framework\Constraint\Constraint; /** - * Concrete assert class. + * Concrete assertion class. * * @internal */ -final class Assert extends PHPUnitAssert +final class Assert implements AssertInterface { + public function assertThat($value, Constraint $constraint, string $message = ''): void + { + PHPUnitAssert::assertThat($value, $constraint, $message); + } + + public function fail(string $message = ''): void + { + PHPUnitAssert::fail($message); + } + + public function incomplete(string $message = ''): void + { + PHPUnitAssert::markTestIncomplete($message); + } + + public function skipped(string $message = ''): void + { + PHPUnitAssert::markTestSkipped($message); + } } diff --git a/src/API/AssertInterface.php b/src/API/AssertInterface.php new file mode 100644 index 0000000..40f07d6 --- /dev/null +++ b/src/API/AssertInterface.php @@ -0,0 +1,43 @@ +assert = $assert; } @@ -248,8 +248,8 @@ final public function withVerifier(string $verifierClass, ...$additionalArgs): s if (isset($this->assert)) { $verifier->setAssert($this->assert); } - if (isset($this->constraint)) { - $verifier->setConstraintFactory($this->constraint); + if (isset($this->constraintFactory)) { + $verifier->setConstraintFactory($this->constraintFactory); } if (isset($this->modifierCondition)) { $verifier->setCondition($this->modifierCondition); @@ -261,7 +261,7 @@ final public function withVerifier(string $verifierClass, ...$additionalArgs): s /** * Get the Assert object. */ - protected function assert(): PHPUnitAssert + protected function assert(): AssertInterface { if (!isset($this->assert)) { $this->assert = new Assert(); diff --git a/src/API/File.php b/src/API/File.php index d13cce3..d5ffe60 100644 --- a/src/API/File.php +++ b/src/API/File.php @@ -11,6 +11,8 @@ * Verify Files. * * Assertions specific to filesystem entries. + * + * @property string $actual */ final class File extends Base { diff --git a/src/API/Method.php b/src/API/Method.php index 00e8bd3..3776eac 100644 --- a/src/API/Method.php +++ b/src/API/Method.php @@ -11,6 +11,8 @@ /** * Verify the return value from class or object methods. + * + * @property class-string|object $actual */ class Method extends Value { diff --git a/src/API/Value.php b/src/API/Value.php index 0bbfec8..3bbb11a 100644 --- a/src/API/Value.php +++ b/src/API/Value.php @@ -77,7 +77,7 @@ final public function attribute(string $attribute): self return $this->constraint( \is_string($this->getActualValue()) ? $this->constraintFactory()->classHasAttribute($attribute) - : $this->constraintFactory()->objectHasAttribute($attribute) + : $this->constraintFactory()->objectHasProperty($attribute) ); } @@ -126,6 +126,8 @@ final public function contain($needle): self { if (\is_string($this->getActualValue())) { try { + \assert(\is_string($needle)); + $constraint = $this->constraintFactory()->stringContains($needle, $this->ignoreCase, $this->ignoreLineEndings); } catch (InvalidConstraintException $e) { throw new InvalidModifierException('Ignoring line endings requires PHPUnit 10. Please upgrade your project requirements.', 0, $e); diff --git a/src/Constraint/Factory.php b/src/Constraint/Factory.php index 65ac927..39d738f 100644 --- a/src/Constraint/Factory.php +++ b/src/Constraint/Factory.php @@ -8,7 +8,6 @@ use BeBat\Verify\Exception\InvalidConstraintException; use DomainException; use PHPUnit\Framework\Constraint as PHPUnitConstraint; -use PHPUnit\Util\Type; use ReflectionClass; use ReflectionParameter; @@ -22,13 +21,34 @@ public function and(PHPUnitConstraint\Constraint ...$constraints): PHPUnitConstr return PHPUnitConstraint\LogicalAnd::fromConstraints(...$constraints); } + public function anything(): PHPUnitConstraint\IsAnything + { + return new PHPUnitConstraint\IsAnything(); + } + + /** + * @template CallbackInput of mixed + * + * @param callable(CallbackInput $callback): bool $callback + * + * @return PHPUnitConstraint\Callback + */ + public function callback(callable $callback): PHPUnitConstraint\Callback + { + return new PHPUnitConstraint\Callback($callback); + } + public function classHasAttribute(string $attribute): PHPUnitConstraint\ClassHasAttribute { + $this->guardUnimplementedConstraint(PHPUnitConstraint\ClassHasAttribute::class); + return new PHPUnitConstraint\ClassHasAttribute($attribute); } public function classHasStaticAttribute(string $attribute): PHPUnitConstraint\ClassHasStaticAttribute { + $this->guardUnimplementedConstraint(PHPUnitConstraint\ClassHasStaticAttribute::class); + return new PHPUnitConstraint\ClassHasStaticAttribute($attribute); } @@ -74,6 +94,8 @@ public function equalTo($value, bool $ignoreCase = false, bool $ignoreLineEnding if ($ignoreLineEndings) { $this->guardUnimplementedConstraint(PHPUnitConstraint\StringEqualsStringIgnoringLineEndings::class); + \assert(\is_string($value)); + return new PHPUnitConstraint\StringEqualsStringIgnoringLineEndings($value); } @@ -183,11 +205,11 @@ public function linkTarget(string $target): FSConstraint\HasLinkTarget /** * @throws InvalidConstraintException if the installed version of PHPUnit does not have this constraint */ - public function list(): PHPUnitConstraint\ArrayIsList + public function list(): PHPUnitConstraint\IsList { - $this->guardUnimplementedConstraint(PHPUnitConstraint\ArrayIsList::class); + $this->guardUnimplementedConstraint(PHPUnitConstraint\IsList::class); - return new PHPUnitConstraint\ArrayIsList(); + return new PHPUnitConstraint\IsList(); } public function matchesFormat(string $format): PHPUnitConstraint\StringMatchesFormatDescription @@ -210,9 +232,24 @@ public function null(): PHPUnitConstraint\IsNull return new PHPUnitConstraint\IsNull(); } - public function objectHasAttribute(string $attribute): PHPUnitConstraint\ObjectHasAttribute + /** + * @deprecated 3.2.0 use objectHasProperty() instead + * + * @return PHPUnitConstraint\ObjectHasAttribute|PHPUnitConstraint\ObjectHasProperty + */ + public function objectHasAttribute(string $attribute) + { + return $this->objectHasProperty($attribute); + } + + /** + * @return PHPUnitConstraint\ObjectHasAttribute|PHPUnitConstraint\ObjectHasProperty + */ + public function objectHasProperty(string $property) { - return new PHPUnitConstraint\ObjectHasAttribute($attribute); + return class_exists(PHPUnitConstraint\ObjectHasProperty::class) + ? new PHPUnitConstraint\ObjectHasProperty($property) + : new PHPUnitConstraint\ObjectHasAttribute($property); } public function or(PHPUnitConstraint\Constraint ...$constraints): PHPUnitConstraint\LogicalOr @@ -299,7 +336,7 @@ public function traversableContains($needle, bool $checkIdentity, bool $checkTyp public function traversableContainsOnly(string $type): PHPUnitConstraint\TraversableContainsOnly { - return new PHPUnitConstraint\TraversableContainsOnly($type, Type::isType($type)); + return new PHPUnitConstraint\TraversableContainsOnly($type, $this->isNativeType($type)); } public function true(): PHPUnitConstraint\IsTrue @@ -308,7 +345,7 @@ public function true(): PHPUnitConstraint\IsTrue } /** - * @param string $type expected type (can be either class name or internal type) + * @param 'array'|'bool'|'boolean'|'callable'|'double'|'float'|'int'|'integer'|'iterable'|'null'|'numeric'|'object'|'real'|'resource (closed)'|'resource'|'scalar'|'string' $type expected type */ public function type(string $type): PHPUnitConstraint\IsType { @@ -330,6 +367,11 @@ public function writable(): PHPUnitConstraint\IsWritable return new PHPUnitConstraint\IsWritable(); } + public function xor(PHPUnitConstraint\Constraint ...$constraints): PHPUnitConstraint\LogicalXor + { + return PHPUnitConstraint\LogicalXor::fromConstraints(...$constraints); + } + /** * Get parameters for IsEqual constructor depending on the version of PHPUnit. * @@ -373,4 +415,34 @@ private function guardUnimplementedConstraint(string $phpunitClass): void throw new InvalidConstraintException("The Constraint {$phpunitClass} does not exist in this version of PHPUnit."); } } + + /** + * Check $type can be verified by an is_*() function. + */ + private function isNativeType(string $type): bool + { + return \in_array( + $type, + [ + 'array', + 'bool', + 'boolean', + 'callable', + 'double', + 'float', + 'int', + 'integer', + 'iterable', + 'null', + 'numeric', + 'object', + 'real', + 'resource (closed)', + 'resource', + 'scalar', + 'string', + ], + true + ); + } } diff --git a/test/AttributeUnitTest.php b/test/AttributeUnitTest.php index 7ddc458..053c9b6 100644 --- a/test/AttributeUnitTest.php +++ b/test/AttributeUnitTest.php @@ -4,6 +4,7 @@ namespace BeBat\Verify\Test; +use BeBat\Verify\API\AssertInterface; use BeBat\Verify\API\Attribute; use BeBat\Verify\API\Value; use BeBat\Verify\Exception\InvalidAttributeException; @@ -13,7 +14,6 @@ use Codeception\AssertThrows; use Mockery; use Mockery\Adapter\Phpunit\MockeryPHPUnitIntegration; -use PHPUnit\Framework\Assert; use PHPUnit\Framework\Constraint\Constraint; use PHPUnit\Framework\TestCase; @@ -30,9 +30,9 @@ final class AttributeUnitTest extends TestCase /** * Stub objects and their attribute names & values. * - * @return array, string, string}> + * @return array|ExampleChild, string, string}> */ - public function attributeValues(): array + public static function provideGetAttributeValueCases(): iterable { return [ 'child private static' => [ExampleChild::class, 'childStaticPrivate', 'child static private property'], @@ -86,12 +86,12 @@ public function testGetAttributeThrowsExceptions(): void * * @param class-string|object $subject * - * @dataProvider attributeValues + * @dataProvider provideGetAttributeValueCases */ public function testGetAttributeValue($subject, string $attributeName, string $attributeValue): void { $verifier = new Value($subject, 'Value'); - $assert = Mockery::mock(Assert::class); + $assert = Mockery::mock(AssertInterface::class); $constraint = Mockery::mock(Constraint::class); $verifier->setAssert($assert); @@ -100,14 +100,14 @@ public function testGetAttributeValue($subject, string $attributeName, string $a ->assertThat($attributeValue, $constraint, '') ->twice(); - $this->assertInstanceOf(Attribute::class, $verifier->attributeNamed($attributeName)->is()->constraint($constraint)); // @phpstan-ignore-line - $this->assertInstanceOf(Attribute::class, $verifier->{$attributeName}->is()->constraint($constraint)); // @phpstan-ignore-line Need to test variable variable access + self::assertInstanceOf(Attribute::class, $verifier->attributeNamed($attributeName)->is()->constraint($constraint)); // @phpstan-ignore-line + self::assertInstanceOf(Attribute::class, $verifier->{$attributeName}->is()->constraint($constraint)); // @phpstan-ignore-line Need to test variable variable access } public function testGetDynamicAttributeValue(): void { $verifier = new Value(new ExampleDynamic(), 'Value'); - $assert = Mockery::mock(Assert::class); + $assert = Mockery::mock(AssertInterface::class); $constraint = Mockery::mock(Constraint::class); $verifier->setAssert($assert); @@ -119,7 +119,7 @@ public function testGetDynamicAttributeValue(): void ->assertThat('parent public property', $constraint, '') ->once(); - $this->assertInstanceOf(Attribute::class, $verifier->dynamicPropertyName->is()->constraint($constraint)); // @phpstan-ignore-line - $this->assertInstanceOf(Attribute::class, $verifier->parentPublic->is()->constraint($constraint)); // @phpstan-ignore-line + self::assertInstanceOf(Attribute::class, $verifier->dynamicPropertyName->is()->constraint($constraint)); // @phpstan-ignore-line + self::assertInstanceOf(Attribute::class, $verifier->parentPublic->is()->constraint($constraint)); // @phpstan-ignore-line } } diff --git a/test/BaseUnitTest.php b/test/BaseUnitTest.php index b91518b..79a9a0e 100644 --- a/test/BaseUnitTest.php +++ b/test/BaseUnitTest.php @@ -5,6 +5,7 @@ namespace BeBat\Verify\Test; use BadMethodCallException; +use BeBat\Verify\API\AssertInterface; use BeBat\Verify\API\Base; use BeBat\Verify\Constraint\Factory as ConstraintFactory; use BeBat\Verify\Exception\InvalidConstraintException; @@ -17,7 +18,6 @@ use Mockery; use Mockery\Adapter\Phpunit\MockeryPHPUnitIntegration; use Mockery\MockInterface; -use PHPUnit\Framework\Assert; use PHPUnit\Framework\Constraint\Constraint; use PHPUnit\Framework\Constraint\LogicalNot; use PHPUnit\Framework\TestCase; @@ -32,7 +32,7 @@ final class BaseUnitTest extends TestCase use AssertThrows; use MockeryPHPUnitIntegration; - /** @var Assert&MockInterface */ + /** @var AssertInterface&MockInterface */ private $mockAssert; /** @var ConstraintFactory&MockInterface */ @@ -43,7 +43,7 @@ final class BaseUnitTest extends TestCase protected function setUp(): void { - $this->mockAssert = Mockery::mock(Assert::class); + $this->mockAssert = Mockery::mock(AssertInterface::class); $this->mockConstraintFactory = Mockery::mock(ConstraintFactory::class); $this->subject = $this->createSubject(); @@ -62,14 +62,14 @@ public function testAssertCustomMessage(): void $this->mockAssert->expects() ->assertThat('test subject', $constraint2, ''); - $this->assertSame( + self::assertSame( $this->subject, // @phpstan-ignore-next-line Custom conjunction method $this->subject->toBeA('custom message value')->constraint($constraint1) ); // test that the message gets reset - $this->assertSame($this->subject, $this->subject->is()->constraint($constraint2)); + self::assertSame($this->subject, $this->subject->is()->constraint($constraint2)); Base::$positiveConjunctions = $backupConjunctions; } @@ -85,7 +85,7 @@ public function testAssertNegativeConstraint(): void $this->mockAssert->expects() ->assertThat('test subject', $negativeConstraint, ''); - $this->assertSame($this->subject, $this->subject->isNot()->constraint($positiveConstraint)); + self::assertSame($this->subject, $this->subject->isNot()->constraint($positiveConstraint)); } public function testAssertPositiveConstraint(): void @@ -95,7 +95,7 @@ public function testAssertPositiveConstraint(): void $this->mockAssert->expects() ->assertThat('test subject', $constraint, ''); - $this->assertSame($this->subject, $this->subject->is()->constraint($constraint)); + self::assertSame($this->subject, $this->subject->is()->constraint($constraint)); } public function testEqualToHandling(): void @@ -129,10 +129,10 @@ public function testEqualToHandling(): void $this->mockAssert->expects() ->assertThat('subject value', $ignoreBothConstraint, ''); - $this->assertSame($verifier, $verifier->is()->equalTo('NO MODIFIERS')); - $this->assertSame($verifier, $verifier->withoutCase()->is()->equalTo('IGNORE CASE')); - $this->assertSame($verifier, $verifier->withoutLineEndings()->is()->equalTo('IGNORE LINE ENDINGS')); - $this->assertSame($verifier, $verifier->withoutCase()->withoutLineEndings()->is()->equalTo('IGNORE BOTH')); + self::assertSame($verifier, $verifier->is()->equalTo('NO MODIFIERS')); + self::assertSame($verifier, $verifier->withoutCase()->is()->equalTo('IGNORE CASE')); + self::assertSame($verifier, $verifier->withoutLineEndings()->is()->equalTo('IGNORE LINE ENDINGS')); + self::assertSame($verifier, $verifier->withoutCase()->withoutLineEndings()->is()->equalTo('IGNORE BOTH')); } public function testEqualToThrowsException(): void @@ -189,7 +189,7 @@ public function testWithVerifier(): void }); // @phpstan-ignore-next-line - $this->assertInstanceOf(ExampleVerifier::class, $this->subject->withVerifier(ExampleVerifier::class)); + self::assertInstanceOf(ExampleVerifier::class, $this->subject->withVerifier(ExampleVerifier::class)); } /** @@ -199,7 +199,7 @@ public function testWithVerifier(): void */ private function createSubject($testValue = 'test subject'): Base { - $subject = new class($testValue, 'Value') extends Base { + $subject = new class ($testValue, 'Value') extends Base { /** * Stub method so we can test performEqualToAssertion. * diff --git a/test/FileFunctionalTest.php b/test/FileFunctionalTest.php index 8f4f51b..d5d63db 100644 --- a/test/FileFunctionalTest.php +++ b/test/FileFunctionalTest.php @@ -34,7 +34,7 @@ public function testContainsAssertion(): void */ public function testContainsModifier(): void { - verify_file($this->fixtureDir . '/File6')->withoutLineEndings()->will()->contain("Is\n\rMy\n\rFile"); + verify_file($this->fixtureDir . '/File6')->withoutLineEndings()->will()->contain("Is\r\nMy\r\nFile"); } /** @@ -92,7 +92,7 @@ public function testOwnershipAssertions(): void $groupInfo = posix_getgrgid($gid); if (!\is_array($userInfo) || !\is_array($groupInfo)) { - $this->markTestIncomplete('Could not get POSIX info for testing.'); + self::markTestIncomplete('Could not get POSIX info for testing.'); } verify_file($this->fixtureDir . '/File1')->has()->owner($userInfo['name']) diff --git a/test/MethodUnitTest.php b/test/MethodUnitTest.php index bc15b22..4046336 100644 --- a/test/MethodUnitTest.php +++ b/test/MethodUnitTest.php @@ -4,6 +4,7 @@ namespace BeBat\Verify\Test; +use BeBat\Verify\API\AssertInterface; use BeBat\Verify\API\Method; use BeBat\Verify\API\ThrownException; use BeBat\Verify\API\Value; @@ -14,7 +15,6 @@ use Codeception\AssertThrows; use Mockery; use Mockery\Adapter\Phpunit\MockeryPHPUnitIntegration; -use PHPUnit\Framework\Assert; use PHPUnit\Framework\AssertionFailedError; use PHPUnit\Framework\Constraint\Constraint; use PHPUnit\Framework\TestCase; @@ -33,9 +33,9 @@ final class MethodUnitTest extends TestCase /** * Stub objects and their method names & values. * - * @return array, string, string}> + * @return array|ExampleChild, string, string}> */ - public function methodValues(): array + public static function provideCallMethodReturnValueCases(): iterable { return [ 'child private static' => [ExampleChild::class, 'childPrivateStaticMethod', 'child private static method'], @@ -54,7 +54,7 @@ public function methodValues(): array public function testAssertingThrownException(): void { $verifier = new Value(new ExampleChild(), 'Value'); - $assert = Mockery::mock(Assert::class); + $assert = Mockery::mock(AssertInterface::class); $exceptionConstraint = Mockery::mock(Constraint::class); $messageConstraint = Mockery::mock(Constraint::class); $codeConstraint = Mockery::mock(Constraint::class); @@ -72,7 +72,7 @@ public function testAssertingThrownException(): void ->assertThat(22, $codeConstraint, '') ->once(); - $this->assertInstanceOf( + self::assertInstanceOf( ThrownException::class, $verifier->childExceptionMethod($exception)->will() // @phpstan-ignore-line ->throwException()->constraint($exceptionConstraint) @@ -85,7 +85,7 @@ public function testCallDynamicMethodReturnValue(): void { $objectVerifier = new Value(new ExampleDynamic(), 'Value'); $classVerifier = new Value(ExampleDynamic::class, 'Value'); - $assert = Mockery::mock(Assert::class); + $assert = Mockery::mock(AssertInterface::class); $constraint = Mockery::mock(Constraint::class); $objectVerifier->setAssert($assert); @@ -104,10 +104,10 @@ public function testCallDynamicMethodReturnValue(): void ->assertThat('parent public static method', $constraint, '') ->once(); - $this->assertInstanceOf(Method::class, $objectVerifier->dynamicMethodName('foo', 'bar')->is()->constraint($constraint)); // @phpstan-ignore-line - $this->assertInstanceOf(Method::class, $objectVerifier->parentPublicMethod()->is()->constraint($constraint)); // @phpstan-ignore-line - $this->assertInstanceOf(Method::class, $classVerifier->dynamicStaticMethod('fizz', 'buzz')->is()->constraint($constraint)); // @phpstan-ignore-line - $this->assertInstanceOf(Method::class, $classVerifier->parentPublicStaticMethod()->is()->constraint($constraint)); // @phpstan-ignore-line + self::assertInstanceOf(Method::class, $objectVerifier->dynamicMethodName('foo', 'bar')->is()->constraint($constraint)); // @phpstan-ignore-line + self::assertInstanceOf(Method::class, $objectVerifier->parentPublicMethod()->is()->constraint($constraint)); // @phpstan-ignore-line + self::assertInstanceOf(Method::class, $classVerifier->dynamicStaticMethod('fizz', 'buzz')->is()->constraint($constraint)); // @phpstan-ignore-line + self::assertInstanceOf(Method::class, $classVerifier->parentPublicStaticMethod()->is()->constraint($constraint)); // @phpstan-ignore-line } /** @@ -115,12 +115,12 @@ public function testCallDynamicMethodReturnValue(): void * * @param class-string|object $subject * - * @dataProvider methodValues + * @dataProvider provideCallMethodReturnValueCases */ public function testCallMethodReturnValue($subject, string $methodName, string $methodValue): void { $verifier = new Value($subject, 'Value'); - $assert = Mockery::mock(Assert::class); + $assert = Mockery::mock(AssertInterface::class); $constraint = Mockery::mock(Constraint::class); $verifier->setAssert($assert); @@ -129,8 +129,8 @@ public function testCallMethodReturnValue($subject, string $methodName, string $ ->assertThat($methodValue, $constraint, '') ->twice(); - $this->assertInstanceOf(Method::class, $verifier->method($methodName)->is()->constraint($constraint)); // @phpstan-ignore-line Yes we want to assert return type - $this->assertInstanceOf(Method::class, $verifier->{$methodName}()->is()->constraint($constraint)); // @phpstan-ignore-line Need to test variable variable access + self::assertInstanceOf(Method::class, $verifier->method($methodName)->is()->constraint($constraint)); // @phpstan-ignore-line Yes we want to assert return type + self::assertInstanceOf(Method::class, $verifier->{$methodName}()->is()->constraint($constraint)); // @phpstan-ignore-line Need to test variable variable access } public function testCallMethodThrowsExceptions(): void diff --git a/test/ValueFunctionalTest.php b/test/ValueFunctionalTest.php index 3887dc0..2e0d7e8 100644 --- a/test/ValueFunctionalTest.php +++ b/test/ValueFunctionalTest.php @@ -63,6 +63,29 @@ public function testBooleanAssertions(): void ->and()->isNot()->null(); } + /** + * @requires PHPUnit < 10 + */ + public function testClassAssertions(): void + { + verify(ExampleChild::class)->has()->attribute('childPublic') // @phpstan-ignore-line + ->and()->attribute('parentPublic') + ->and()->staticAttribute('childStaticPublic') + ->and()->staticAttribute('parentStaticPublic') + ->and()->doesNot()->have()->attribute('someOtherAttributeNate') + ->and()->doesNot()->have()->staticAttribute('someStaticAttribute') + ->childStaticPublic->is()->identicalTo('child static public property') + ->childStaticProtected->is()->identicalTo('child static protected property') + ->childStaticPrivate->is()->identicalTo('child static private property') + ->parentStaticPublic->is()->identicalTo('parent static public property') + ->parentStaticProtected->is()->identicalTo('parent static protected property') + ->childPublicStaticMethod()->is()->identicalTo('child public static method') + ->childProtectedStaticMethod()->is()->identicalTo('child protected static method') + ->childPrivateStaticMethod()->is()->identicalTo('child private static method') + ->parentPublicStaticMethod()->is()->identicalTo('parent public static method') + ->parentProtectedStaticMethod()->is()->identicalTo('parent protected static method'); + } + /** * @requires PHPUnit >= 9 */ @@ -92,7 +115,7 @@ public function testClosedResourceException(): void */ public function testContainIgnoresLineEndings(): void { - verify("This\n\ris\n\rmy\n\rstring")->withoutLineEndings()->will()->contain("my\nstring"); + verify("This\r\nis\r\nmy\r\nstring")->withoutLineEndings()->will()->contain("my\nstring"); } /** @@ -161,10 +184,10 @@ public function testListAssertion(): void public function testNumericAssertions(): void { - verify(NAN)->is()->nan() + verify(\NAN)->is()->nan() ->and()->isNot()->infinite() ->and()->isNot()->finite(); - verify(INF)->is()->infinite() + verify(\INF)->is()->infinite() ->and()->isNot()->finite() ->and()->isNot()->nan(); @@ -200,22 +223,6 @@ public function testObjectAssertions(): void ->parentProtectedMethod()->is()->identicalTo('parent protected method') ->childPassthruMethod()->with('some value')->is()->identicalTo('some value') ->with('a different value')->is()->identicalTo('a different value'); - verify(ExampleChild::class)->has()->attribute('childPublic') // @phpstan-ignore-line - ->and()->attribute('parentPublic') - ->and()->staticAttribute('childStaticPublic') - ->and()->staticAttribute('parentStaticPublic') - ->and()->doesNot()->have()->attribute('someOtherAttributeNate') - ->and()->doesNot()->have()->staticAttribute('someStaticAttribute') - ->childStaticPublic->is()->identicalTo('child static public property') - ->childStaticProtected->is()->identicalTo('child static protected property') - ->childStaticPrivate->is()->identicalTo('child static private property') - ->parentStaticPublic->is()->identicalTo('parent static public property') - ->parentStaticProtected->is()->identicalTo('parent static protected property') - ->childPublicStaticMethod()->is()->identicalTo('child public static method') - ->childProtectedStaticMethod()->is()->identicalTo('child protected static method') - ->childPrivateStaticMethod()->is()->identicalTo('child private static method') - ->parentPublicStaticMethod()->is()->identicalTo('parent public static method') - ->parentProtectedStaticMethod()->is()->identicalTo('parent protected static method'); } public function testStringAssertions(): void @@ -224,12 +231,12 @@ public function testStringAssertions(): void ->and()->withoutCase()->contain('my string') ->and()->startWith('This') ->and()->endWith('String') - ->and()->matchRegExp('/\bIs\s+My\b/') + ->and()->matchRegExp('/\\bIs\\s+My\\b/') ->and()->willNot()->contain('MY STRING') ->and()->withoutCase()->willNot()->contain('another string') ->and()->willNot()->startWith('That') ->and()->willNot()->endWith('Sting') - ->and()->willNot()->matchRegExp('/\d+/'); + ->and()->willNot()->matchRegExp('/\\d+/'); verify("foo/24 bada55\n") ->will()->matchFormat('foo%e%d%w%x') From 6aa6f8f4ffce7e053e3f25b341884ae16b0f7e20 Mon Sep 17 00:00:00 2001 From: Ben Batschelet Date: Tue, 15 Aug 2023 01:17:16 -0500 Subject: [PATCH 2/9] Add deprecation errors to attribute methods --- src/API/Attribute.php | 104 +---------------- src/API/Property.php | 112 ++++++++++++++++++ src/API/Value.php | 50 +++++++-- src/Exception/InvalidPropertyException.php | 11 ++ test/AttributeUnitTest.php | 125 --------------------- test/PropertyUnitTest.php | 120 ++++++++++++++++++++ 6 files changed, 288 insertions(+), 234 deletions(-) create mode 100644 src/API/Property.php create mode 100644 src/Exception/InvalidPropertyException.php delete mode 100644 test/AttributeUnitTest.php create mode 100644 test/PropertyUnitTest.php diff --git a/src/API/Attribute.php b/src/API/Attribute.php index 0cdb406..ac8cc55 100644 --- a/src/API/Attribute.php +++ b/src/API/Attribute.php @@ -4,111 +4,13 @@ namespace BeBat\Verify\API; -use BeBat\Verify\Exception\InvalidAttributeException; -use BeBat\Verify\Exception\InvalidSubjectException; -use ReflectionClass; -use ReflectionProperty; - /** * Verify class & object attributes. * + * @deprecated 3.2.0 use Property instead + * * @property class-string|object $actual */ -final class Attribute extends Value +final class Attribute extends Property { - /** - * Name of object attribute to evaluate. - * - * @var string - */ - private $attributeName; - - /** - * Cached version of value from attribute. - * - * @var mixed - */ - private $resolvedValue; - - /** - * Has the value been resolved from the object or class. - * - * @var bool - */ - private $valueResolved = false; - - /** - * @param class-string|object $actual - */ - public function __construct($actual, string $name, string $attributeName) - { - if (\is_string($actual)) { - if (!class_exists($actual)) { - throw new InvalidSubjectException("Could not find class \"{$actual}\"."); - } - } elseif (!\is_object($actual)) { - throw new InvalidSubjectException('Subject must be either an object or class name.'); - } - - $name = \is_string($actual) ? "{$name}::{$attributeName}" : "{$name}->{$attributeName}"; - - parent::__construct($actual, $name); - - $this->attributeName = $attributeName; - } - - /** - * Get the value for an attribute. - * - * @return mixed - * - * @throws InvalidAttributeException if subject doesn't have the named attribute - */ - protected function getActualValue() - { - if (!$this->valueResolved) { - $this->resolvedValue = $this->readAttribute(); - } - - return $this->resolvedValue; - } - - /** - * Returns the value of an attribute of a class or an object. - * This also works for attributes that are declared protected or private. - * - * @return mixed - * - * @throws InvalidSubjectException if subject is not a class name or object - */ - private function readAttribute() - { - $reflector = new ReflectionClass($this->actual); - $properties = \is_object($this->actual) - ? $reflector->getProperties() - : $reflector->getProperties(ReflectionProperty::IS_STATIC); - - foreach ($properties as $property) { - if ($property->getName() === $this->attributeName) { - $this->valueResolved = true; - - if (!$property->isPublic()) { - $property->setAccessible(true); - } - - return \is_object($this->actual) - ? $property->getValue($this->actual) - : $property->getValue(); - } - } - - if (\is_object($this->actual) && $reflector->hasMethod('__get')) { - $this->valueResolved = true; - - // @phpstan-ignore-next-line - return $this->actual->{$this->attributeName}; - } - - throw new InvalidAttributeException("Could not find attribute \"{$this->attributeName}\"."); - } } diff --git a/src/API/Property.php b/src/API/Property.php new file mode 100644 index 0000000..363c37e --- /dev/null +++ b/src/API/Property.php @@ -0,0 +1,112 @@ +{$propertyName}"; + + parent::__construct($actual, $name); + + $this->propertyName = $propertyName; + } + + /** + * Get the value for the property. + * + * @return mixed + */ + protected function getActualValue() + { + if (!$this->valueResolved) { + $this->resolvedValue = $this->readProperty(); + } + + return $this->resolvedValue; + } + + /** + * Returns the value of a property of a class or an object. + * This also works for properties that are declared protected or private. + * + * @return mixed + * + * @throws InvalidPropertyException if the property doesn't exist + */ + private function readProperty() + { + $reflector = new ReflectionClass($this->actual); + $properties = \is_object($this->actual) + ? $reflector->getProperties() + : $reflector->getProperties(ReflectionProperty::IS_STATIC); + + foreach ($properties as $property) { + if ($property->getName() === $this->propertyName) { + $this->valueResolved = true; + + if (!$property->isPublic()) { + $property->setAccessible(true); + } + + return \is_object($this->actual) + ? $property->getValue($this->actual) + : $property->getValue(); + } + } + + if (\is_object($this->actual) && $reflector->hasMethod('__get')) { + $this->valueResolved = true; + + // @phpstan-ignore-next-line + return $this->actual->{$this->propertyName}; + } + + throw new InvalidPropertyException("Could not find property \"{$this->propertyName}\"."); + } +} diff --git a/src/API/Value.php b/src/API/Value.php index 3bbb11a..58a9adf 100644 --- a/src/API/Value.php +++ b/src/API/Value.php @@ -52,11 +52,11 @@ public function __call(string $method, array $arguments = []): Base } /** - * Switch to Attribute verifier. + * Switch to Property verifier. */ - final public function __get(string $attribute): Attribute + final public function __get(string $property): Property { - return $this->attributeNamed($attribute); + return $this->propertyNamed($property); } /** @@ -70,22 +70,26 @@ final public function array(): self /** * Assert SUT does or does not have a given class attribute. * + * @deprecated 3.2.0 use property() instead + * * @param string $attribute Name of attribute expected to be in SUT */ final public function attribute(string $attribute): self { - return $this->constraint( - \is_string($this->getActualValue()) - ? $this->constraintFactory()->classHasAttribute($attribute) - : $this->constraintFactory()->objectHasProperty($attribute) - ); + @trigger_error('Using verify()->attribute() is deprecated, use verify()->property() instead.', \E_USER_DEPRECATED); + + return $this->property($attribute); } /** * Switch to Attribute verifier. + * + * @deprecated 3.2.0 use propertyNamed() instead */ final public function attributeNamed(string $attribute): Attribute { + @trigger_error('Using verify()->attributeNamed() is deprecated, use verify()->propertyNamed() instead.', \E_USER_DEPRECATED); + return $this->withVerifier(Attribute::class, $attribute); } @@ -467,6 +471,32 @@ final public function object(): self return $this->constraint($this->constraintFactory()->type(IsType::TYPE_OBJECT)); } + /** + * Assert that SUT does or does not have a given class/object property. + * + * @param string $property Name of property expected to be in SUT + */ + final public function property(string $property): self + { + if (\is_string($this->getActualValue())) { + @trigger_error('Verifying properties on a class has been deprecated.', \E_USER_DEPRECATED); + } + + return $this->constraint( + \is_string($this->getActualValue()) + ? $this->constraintFactory()->classHasAttribute($property) + : $this->constraintFactory()->objectHasProperty($property) + ); + } + + /** + * Switch to Property verifier. + */ + final public function propertyNamed(string $property): Property + { + return $this->withVerifier(Property::class, $property); + } + /** * Assert that SUT is or is not a resource. */ @@ -506,10 +536,14 @@ final public function startWith(string $prefix): self /** * Assert SUT does or does not have a given static attribute. * + * @deprecated 3.2.0 + * * @param string $attribute Name of attribute expected to be in SUT */ final public function staticAttribute(string $attribute): self { + @trigger_error('Verifying static properties on a class has been deprecated', \E_USER_DEPRECATED); + return $this->constraint($this->constraintFactory()->classHasStaticAttribute($attribute)); } diff --git a/src/Exception/InvalidPropertyException.php b/src/Exception/InvalidPropertyException.php new file mode 100644 index 0000000..758978e --- /dev/null +++ b/src/Exception/InvalidPropertyException.php @@ -0,0 +1,11 @@ +|ExampleChild, string, string}> - */ - public static function provideGetAttributeValueCases(): iterable - { - return [ - 'child private static' => [ExampleChild::class, 'childStaticPrivate', 'child static private property'], - 'child protected static' => [ExampleChild::class, 'childStaticProtected', 'child static protected property'], - 'child public static' => [ExampleChild::class, 'childStaticPublic', 'child static public property'], - 'parent protected static' => [ExampleChild::class, 'parentStaticProtected', 'parent static protected property'], - 'parent public static' => [ExampleChild::class, 'parentStaticPublic', 'parent static public property'], - 'child private' => [new ExampleChild(), 'childPrivate', 'child private property'], - 'child protected' => [new ExampleChild(), 'childProtected', 'child protected property'], - 'child public' => [new ExampleChild(), 'childPublic', 'child public property'], - 'parent protected' => [new ExampleChild(), 'parentProtected', 'parent protected property'], - 'parent public' => [new ExampleChild(), 'parentPublic', 'parent public property'], - ]; - } - - public function testGetAttributeThrowsExceptions(): void - { - $constraint = Mockery::mock(Constraint::class); - - $subject = new Value(new ExampleChild(), 'Value'); - $exception = new InvalidAttributeException('Could not find attribute "someMissingProperty".'); - - $this->assertThrows($exception, static function () use ($subject, $constraint): void { - $subject->attributeNamed('someMissingProperty')->is()->constraint($constraint); - }); - - $subject = new Value(ExampleChild::class, 'Value'); - $exception = new InvalidAttributeException('Could not find attribute "missingStaticProperty".'); - - $this->assertThrows($exception, static function () use ($subject, $constraint): void { - $subject->attributeNamed('missingStaticProperty')->is()->constraint($constraint); - }); - - $subject = new Value('\\Some\\Fake\\Class\\Name', 'Value'); - $exception = new InvalidSubjectException('Could not find class "\\Some\\Fake\\Class\\Name".'); - - $this->assertThrows($exception, static function () use ($subject): void { - $subject->attributeNamed('somePropertyName'); - }); - - $subject = new Value(5, 'Value'); - $exception = new InvalidSubjectException('Subject must be either an object or class name.'); - - $this->assertThrows($exception, static function () use ($subject): void { - $subject->attributeNamed('somePropertyName'); - }); - } - - /** - * Test reading attribute values. - * - * @param class-string|object $subject - * - * @dataProvider provideGetAttributeValueCases - */ - public function testGetAttributeValue($subject, string $attributeName, string $attributeValue): void - { - $verifier = new Value($subject, 'Value'); - $assert = Mockery::mock(AssertInterface::class); - $constraint = Mockery::mock(Constraint::class); - - $verifier->setAssert($assert); - - $assert->expects() - ->assertThat($attributeValue, $constraint, '') - ->twice(); - - self::assertInstanceOf(Attribute::class, $verifier->attributeNamed($attributeName)->is()->constraint($constraint)); // @phpstan-ignore-line - self::assertInstanceOf(Attribute::class, $verifier->{$attributeName}->is()->constraint($constraint)); // @phpstan-ignore-line Need to test variable variable access - } - - public function testGetDynamicAttributeValue(): void - { - $verifier = new Value(new ExampleDynamic(), 'Value'); - $assert = Mockery::mock(AssertInterface::class); - $constraint = Mockery::mock(Constraint::class); - - $verifier->setAssert($assert); - - $assert->expects() - ->assertThat('get dynamicPropertyName', $constraint, '') - ->once(); - $assert->expects() - ->assertThat('parent public property', $constraint, '') - ->once(); - - self::assertInstanceOf(Attribute::class, $verifier->dynamicPropertyName->is()->constraint($constraint)); // @phpstan-ignore-line - self::assertInstanceOf(Attribute::class, $verifier->parentPublic->is()->constraint($constraint)); // @phpstan-ignore-line - } -} diff --git a/test/PropertyUnitTest.php b/test/PropertyUnitTest.php new file mode 100644 index 0000000..c7654ee --- /dev/null +++ b/test/PropertyUnitTest.php @@ -0,0 +1,120 @@ +|ExampleChild, string, string}> + */ + public static function provideGetPropertyValueCases(): iterable + { + return [ + 'child private' => [new ExampleChild(), 'childPrivate', 'child private property'], + 'child protected' => [new ExampleChild(), 'childProtected', 'child protected property'], + 'child public' => [new ExampleChild(), 'childPublic', 'child public property'], + 'parent protected' => [new ExampleChild(), 'parentProtected', 'parent protected property'], + 'parent public' => [new ExampleChild(), 'parentPublic', 'parent public property'], + ]; + } + + public function testGetDynamicPropertyValue(): void + { + $verifier = new Value(new ExampleDynamic(), 'Value'); + $assert = Mockery::mock(AssertInterface::class); + $constraint = Mockery::mock(Constraint::class); + + $verifier->setAssert($assert); + + $assert->expects() + ->assertThat('get dynamicPropertyName', $constraint, '') + ->once(); + $assert->expects() + ->assertThat('parent public property', $constraint, '') + ->once(); + + self::assertInstanceOf(Property::class, $verifier->dynamicPropertyName->is()->constraint($constraint)); // @phpstan-ignore-line + self::assertInstanceOf(Property::class, $verifier->parentPublic->is()->constraint($constraint)); // @phpstan-ignore-line + } + + public function testGetPropertyThrowsExceptions(): void + { + $constraint = Mockery::mock(Constraint::class); + + $subject = new Value(new ExampleChild(), 'Value'); + $exception = new InvalidPropertyException('Could not find property "someMissingProperty".'); + + $this->assertThrows($exception, static function () use ($subject, $constraint): void { + $subject->propertyNamed('someMissingProperty')->is()->constraint($constraint); + }); + + $subject = new Value(ExampleChild::class, 'Value'); + $exception = new InvalidPropertyException('Could not find property "missingStaticProperty".'); + + $this->assertThrows($exception, static function () use ($subject, $constraint): void { + $subject->propertyNamed('missingStaticProperty')->is()->constraint($constraint); + }); + + $subject = new Value('\\Some\\Fake\\Class\\Name', 'Value'); + $exception = new InvalidSubjectException('Could not find class "\\Some\\Fake\\Class\\Name".'); + + $this->assertThrows($exception, static function () use ($subject): void { + $subject->propertyNamed('somePropertyName'); + }); + + $subject = new Value(5, 'Value'); + $exception = new InvalidSubjectException('Subject must be either an object or class name.'); + + $this->assertThrows($exception, static function () use ($subject): void { + $subject->propertyNamed('somePropertyName'); + }); + } + + /** + * Test reading property values. + * + * @param class-string|object $subject + * + * @dataProvider provideGetPropertyValueCases + */ + public function testGetPropertyValue($subject, string $propertyName, string $propertyValue): void + { + $verifier = new Value($subject, 'Value'); + $assert = Mockery::mock(AssertInterface::class); + $constraint = Mockery::mock(Constraint::class); + + $verifier->setAssert($assert); + + $assert->expects() + ->assertThat($propertyValue, $constraint, '') + ->twice(); + + self::assertInstanceOf(Property::class, $verifier->propertyNamed($propertyName)->is()->constraint($constraint)); // @phpstan-ignore-line + self::assertInstanceOf(Property::class, $verifier->{$propertyName}->is()->constraint($constraint)); // @phpstan-ignore-line Need to test variable variable access + } +} From 1f161e53990a337e069db4dc0b2dcf76d4a7a8e8 Mon Sep 17 00:00:00 2001 From: Ben Batschelet Date: Tue, 15 Aug 2023 19:06:22 -0500 Subject: [PATCH 3/9] Add unit tests for exceptions --- .github/workflows/acceptance.yml | 2 +- phpstan.neon.dist | 6 ++++ src/API/Value.php | 18 ++++++++---- test/ValueFunctionalTest.php | 35 ++++++++++++++++++------ test/_fixtures/Classes/ExampleChild.php | 2 +- test/_fixtures/Classes/ExampleParent.php | 2 +- 6 files changed, 49 insertions(+), 16 deletions(-) diff --git a/.github/workflows/acceptance.yml b/.github/workflows/acceptance.yml index 040f0fd..8595ed6 100644 --- a/.github/workflows/acceptance.yml +++ b/.github/workflows/acceptance.yml @@ -81,7 +81,7 @@ jobs: - name: Install Dependencies run: composer install --no-interaction --no-progress - - name: Chech composer.json Format + - name: Check composer.json Format run: composer normalize --diff --dry-run --no-interaction --ansi - name: Check Code Style diff --git a/phpstan.neon.dist b/phpstan.neon.dist index f38ee70..d456a35 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -15,3 +15,9 @@ parameters: - message: '/Cannot call method get(?:NumberOf)?Parameters\(\) on ReflectionMethod\|null/' path: %currentWorkingDirectory%/src/Constraint/Factory.php + - + message: '/Call to deprecated method .* of class BeBat\\Verify\\/' + path: %currentWorkingDirectory%/test + - + message: '/Access to an undefined property BeBat\\Verify\\API\\Value::/' + path: %currentWorkingDirectory%/test/ValueFunctionalTest.php diff --git a/src/API/Value.php b/src/API/Value.php index 58a9adf..1d24217 100644 --- a/src/API/Value.php +++ b/src/API/Value.php @@ -482,11 +482,15 @@ final public function property(string $property): self @trigger_error('Verifying properties on a class has been deprecated.', \E_USER_DEPRECATED); } - return $this->constraint( - \is_string($this->getActualValue()) + try { + $constraint = \is_string($this->getActualValue()) ? $this->constraintFactory()->classHasAttribute($property) - : $this->constraintFactory()->objectHasProperty($property) - ); + : $this->constraintFactory()->objectHasProperty($property); + } catch (InvalidConstraintException $e) { + throw new BadMethodCallException('Asserting a class has a property is no longer supported in PHPUnit 10. You should update your test.', 0, $e); + } + + return $this->constraint($constraint); } /** @@ -544,7 +548,11 @@ final public function staticAttribute(string $attribute): self { @trigger_error('Verifying static properties on a class has been deprecated', \E_USER_DEPRECATED); - return $this->constraint($this->constraintFactory()->classHasStaticAttribute($attribute)); + try { + return $this->constraint($this->constraintFactory()->classHasStaticAttribute($attribute)); + } catch (InvalidConstraintException $e) { + throw new BadMethodCallException('Asserting a class has a property is no longer supported in PHPUnit 10. You should update your test.', 0, $e); + } } /** diff --git a/test/ValueFunctionalTest.php b/test/ValueFunctionalTest.php index 2e0d7e8..063cabb 100644 --- a/test/ValueFunctionalTest.php +++ b/test/ValueFunctionalTest.php @@ -66,14 +66,19 @@ public function testBooleanAssertions(): void /** * @requires PHPUnit < 10 */ - public function testClassAssertions(): void + public function testClassPropertyAssertions(): void { - verify(ExampleChild::class)->has()->attribute('childPublic') // @phpstan-ignore-line - ->and()->attribute('parentPublic') + verify(ExampleChild::class)->has()->property('childPublic') + ->and()->property('parentPublic') ->and()->staticAttribute('childStaticPublic') ->and()->staticAttribute('parentStaticPublic') - ->and()->doesNot()->have()->attribute('someOtherAttributeNate') - ->and()->doesNot()->have()->staticAttribute('someStaticAttribute') + ->and()->doesNot()->have()->property('someOtherAttributeNate') + ->and()->doesNot()->have()->staticAttribute('someStaticAttribute'); + } + + public function testClassStaticMethods(): void + { + verify(ExampleChild::class) ->childStaticPublic->is()->identicalTo('child static public property') ->childStaticProtected->is()->identicalTo('child static protected property') ->childStaticPrivate->is()->identicalTo('child static private property') @@ -208,9 +213,9 @@ public function testObjectAssertions(): void { $object = new ExampleChild(); - verify($object)->has()->attribute('childPublic') // @phpstan-ignore-line - ->and()->attribute('parentPublic') - ->and()->doesNot()->have()->attribute('someOtherAttributeName') + verify($object)->has()->property('childPublic') + ->and()->property('parentPublic') + ->and()->doesNot()->have()->property('someOtherAttributeName') ->childPublic->is()->identicalTo('child public property') ->childProtected->is()->identicalTo('child protected property') ->childPrivate->is()->identicalTo('child private property') @@ -225,6 +230,20 @@ public function testObjectAssertions(): void ->with('a different value')->is()->identicalTo('a different value'); } + /** + * @requires PHPUnit >= 10 + */ + public function testPropertyThrowsException(): void + { + $this->assertThrows(BadMethodCallException::class, static function (): void { + verify(ExampleChild::class)->has()->property('childPublic'); + }); + + $this->assertThrows(BadMethodCallException::class, static function (): void { + verify(ExampleChild::class)->has()->staticAttribute('childStaticPublic'); + }); + } + public function testStringAssertions(): void { verify('This Is My String')->will()->contain('This Is') diff --git a/test/_fixtures/Classes/ExampleChild.php b/test/_fixtures/Classes/ExampleChild.php index 00ed0bb..c8775c6 100644 --- a/test/_fixtures/Classes/ExampleChild.php +++ b/test/_fixtures/Classes/ExampleChild.php @@ -7,7 +7,7 @@ use Throwable; /** - * Example class for testing attribute access. + * Example class for testing property access. * * @internal */ diff --git a/test/_fixtures/Classes/ExampleParent.php b/test/_fixtures/Classes/ExampleParent.php index e1a69b8..5655711 100644 --- a/test/_fixtures/Classes/ExampleParent.php +++ b/test/_fixtures/Classes/ExampleParent.php @@ -5,7 +5,7 @@ namespace BeBat\Verify\Test\Examples; /** - * Example class for testing attribute access. + * Example class for testing property access. * * @internal */ From d4f594d40f3bd343c3fae71fc48b0aa2f71c40a1 Mon Sep 17 00:00:00 2001 From: Ben Batschelet Date: Wed, 16 Aug 2023 12:17:13 -0500 Subject: [PATCH 4/9] Documentation for attribute deprecation --- docs/_static/verify.css | 28 +++++++++++++++++++++++++ docs/assertions.rst | 21 +++++++++++++++++-- docs/conf.py | 2 +- docs/getting-started.rst | 6 ++++-- docs/index.rst | 2 +- docs/methods.rst | 2 +- docs/{attributes.rst => properties.rst} | 16 +++++++------- docs/requirements.txt | 4 ++-- 8 files changed, 64 insertions(+), 17 deletions(-) rename docs/{attributes.rst => properties.rst} (60%) diff --git a/docs/_static/verify.css b/docs/_static/verify.css index abc9e62..f13e49f 100644 --- a/docs/_static/verify.css +++ b/docs/_static/verify.css @@ -15,3 +15,31 @@ a:hover { color: #e74c3c; font-weight: normal; } + +.rst-content .deprecated { + background-color: #ffedcc; + padding: 12px; + margin-bottom: 24px; +} + +.deprecated .versionmodified.deprecated { + display: block; + background-color: #f0b37e; + color: #fff; + padding: 6px 12px; + margin: -12px -12px 12px; + font-style: normal; +} + +.deprecated .versionmodified.deprecated::before { + font-family: FontAwesome; + display: inline-block; + font-weight: 400; + line-height: 1; + content: ""; + margin-right: 4px; +} + +.deprecated > p { + margin: 0; +} diff --git a/docs/assertions.rst b/docs/assertions.rst index 36fe9eb..bf98228 100644 --- a/docs/assertions.rst +++ b/docs/assertions.rst @@ -395,8 +395,22 @@ Object & Class Properties verify($subject)->has()->attribute('attributeName'); verify(MyClass::class)->has()->attribute('attributeName'); -.. note:: - The :php:`attribute()` assertion works with both classes and object instances. +.. deprecated:: 3.2.0 + The :php:`attribute()` assertion has been replaced by :phpref:`property() ` + +.. phpref:: assertions.value.property + +:php:`property()` +~~~~~~~~~~~~~~~~~ + +.. code-block:: php + :caption: Assert that subject has some property + + verify($subject)->has()->property('propertyName'); + +.. deprecated:: 3.2.0 + Using the :php:`property()` assertion with a class string as the subject has been deprecated. Assertions with object instances as the subject will continue to be supported. + :php:`staticAttribute()` ~~~~~~~~~~~~~~~~~~~~~~~~ @@ -406,6 +420,9 @@ Object & Class Properties verify(MyClass::class)->has()->staticAttribute('attributeName'); +.. deprecated:: 3.2.0 + Making assertions about static attributes has been deprecated. + .. _assertions.section.value.json: JSON diff --git a/docs/conf.py b/docs/conf.py index bd7c106..fb64d12 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -49,7 +49,7 @@ def get_version(): 'php': {'startinline': True}, } -intersphinx_mapping = {'phpunit': ('https://phpunit.readthedocs.io/en/9.5/', None)} +intersphinx_mapping = {'phpunit': ('https://docs.phpunit.de/en/10.3/', None)} # -- Options for HTML output ------------------------------------------------- diff --git a/docs/getting-started.rst b/docs/getting-started.rst index 3cd5b36..3265cb8 100644 --- a/docs/getting-started.rst +++ b/docs/getting-started.rst @@ -20,7 +20,9 @@ BeBat/Verify will be added to your :file:`composer.json` under :code:`require-de Compatibility ------------- -BeBat/Verify is built on top of PHPUnit's own assertions. It is compatible with any version of PHPUnit 8 or 9 and has preliminary support for version 10. It should also be compatible with the current version of `Codeception `__. Some assertions have been removed from later versions of PHPUnit, and others added. When using BeBat/Verify you should explicitly declare what major version of PHPUnit your project depends on so that there are no surprise compatibility issues. See the :ref:`available assertions ` to see what assertions are compatible with your version of PHPUnit. +BeBat/Verify is built on top of PHPUnit's own assertions. It is compatible with any version of PHPUnit 8, 9 or 10.1 and above. It should also be compatible with the current version of `Codeception `__. + +Some assertions have been removed from later versions of PHPUnit, and others added. When using BeBat/Verify you should explicitly declare what major version of PHPUnit your project depends on so that there are no surprise compatibility issues. See the :ref:`available assertions ` to see what assertions are compatible with your version of PHPUnit. In addition, BeBat/Verify is compatible with both PHP 7.2+ and 8+. @@ -31,7 +33,7 @@ BeBat/Verify uses namespaced functions, so to include it in your unit tests you .. code-block:: php - // assertions for code values + // assertions for values in code use function BeBat\Verify\verify; // assertions for files diff --git a/docs/index.rst b/docs/index.rst index fdc0977..527cf3e 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -13,7 +13,7 @@ BeBat/Verify is a small wrapper for PHPUnit's assertions, intended to make your assertions modifiers chaining - attributes + properties methods .. toctree:: diff --git a/docs/methods.rst b/docs/methods.rst index 2930df0..40aef40 100644 --- a/docs/methods.rst +++ b/docs/methods.rst @@ -5,7 +5,7 @@ Method Assertions ================= -Just like with :ref:`attributes `, assertions can be made about an object's methods by adding the method call after :php:`verify()`. You can write assertions about either a method's return value or an exception that the method throws. +Just like with :ref:`properties `, assertions can be made about an object's methods by adding the method call after :php:`verify()`. You can write assertions about either a method's return value or an exception that the method throws. Return Values ============= diff --git a/docs/attributes.rst b/docs/properties.rst similarity index 60% rename from docs/attributes.rst rename to docs/properties.rst index f99f748..98447cd 100644 --- a/docs/attributes.rst +++ b/docs/properties.rst @@ -1,13 +1,13 @@ .. role:: php(code) :language: php -.. _attributes: +.. _properties: -==================== -Attribute Assertions -==================== +=================== +Property Assertions +=================== -BeBat/Verify has the ability to test the value of object and class properties (or "attributes"), even those that are protected or private. While writing assertions about a subject's internal state is not generally good practice, there are times when inspecting a protected value may be the simplest way of checking your code. The attribute you wish to check can be tacked on after calling :php:`verify()`, just like if you were accessing it as a public value. +BeBat/Verify has the ability to test the value of object and class properties, even those that are protected or private. While writing assertions about a subject's internal state is not generally good practice, there are times when inspecting a protected value may be the simplest way of checking your code. The property you wish to check can be tacked on after calling :php:`verify()`, just like if you were accessing it as a public value. For example, if you had an object called :php:`$user` with a :php:`first_name` property that should be equal to :php:`'Alice'`, you can assert that with the following code: @@ -21,13 +21,13 @@ A similar assertion about a class's static properties might look like the follow verify(Model::class)->dbc->is()->resource(); -If you would rather explicitly identify your attribute/property, you can do so with the :php:`attributeNamed()` method: +If you would rather explicitly identify your property, you can do so with the :php:`propertyNamed()` method: .. code-block:: php - verify($obj)->attributeNamed('fooBar')->is()->false(); + verify($obj)->propertyNamed('fooBar')->is()->false(); -All of BeBat/Verify's assertions should be compatible with reading object or class attributes. In addition, attributes fully support chaining and assertion modifiers. The only exception is that once your chain contains an attribute, you can no longer add assertions about their containing object. Put another way, always write your assertions about an object *first* before writing any about its attributes. For example: +All of BeBat/Verify's assertions should be compatible with reading object or class properties. In addition, properties fully support chaining and assertion modifiers. The only exception is that once your chain contains an attribute, you can no longer add assertions about their containing object. Put another way, always write your assertions about an object *first* before writing any about its properties. For example: .. code-block:: php diff --git a/docs/requirements.txt b/docs/requirements.txt index 0637ef7..d825cfc 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,3 +1,3 @@ Sphinx==4.5.0 -sphinx-rtd-theme==1.0.0 -sphinxcontrib-phpdomain==0.8.0 +sphinx-rtd-theme==1.2.2 +sphinxcontrib-phpdomain==0.9.0 From 4d508a4fcff1d838f2bdba2939426136f2910089 Mon Sep 17 00:00:00 2001 From: Ben Batschelet Date: Wed, 16 Aug 2023 14:22:32 -0500 Subject: [PATCH 5/9] Update compatibility info --- README.md | 4 ++-- docs/getting-started.rst | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 3ed0217..fe43273 100644 --- a/README.md +++ b/README.md @@ -25,9 +25,9 @@ BeBat/Verify will be added to your `composer.json` under `require-dev` and insta ## Compatibility -BeBat/Verify is built on top of PHPUnit's own assertions. It is compatible with any version of PHPUnit 8 or 9 and has preliminary support for version 10. Some assertions have been removed from later versions of PHPUnit, and others added. Those assertions are noted in documentation below. When using Verify you should explicitly declare what version of PHPUnit your project depends on so that there are no surprise compatibility issues. +BeBat/Verify is built on top of PHPUnit's own assertions. It is compatible with any version of PHPUnit 8, 9, or 10.1 and above. It should also be compatible with the current version of [Codeception](https://codeception.com/). -In addition, Verify is compatible with both PHP 7.2+ and 8+. +In addition, BeBat/Verify is compatible with both PHP 7.2+ and 8+. # Documentation diff --git a/docs/getting-started.rst b/docs/getting-started.rst index 3265cb8..20ff0bc 100644 --- a/docs/getting-started.rst +++ b/docs/getting-started.rst @@ -20,7 +20,7 @@ BeBat/Verify will be added to your :file:`composer.json` under :code:`require-de Compatibility ------------- -BeBat/Verify is built on top of PHPUnit's own assertions. It is compatible with any version of PHPUnit 8, 9 or 10.1 and above. It should also be compatible with the current version of `Codeception `__. +BeBat/Verify is built on top of PHPUnit's own assertions. It is compatible with any version of PHPUnit 8, 9, or 10.1 and above. It should also be compatible with the current version of `Codeception `__. Some assertions have been removed from later versions of PHPUnit, and others added. When using BeBat/Verify you should explicitly declare what major version of PHPUnit your project depends on so that there are no surprise compatibility issues. See the :ref:`available assertions ` to see what assertions are compatible with your version of PHPUnit. From d055ba2fd231ac7be5da877ef3d328833c864929 Mon Sep 17 00:00:00 2001 From: Ben Batschelet Date: Wed, 16 Aug 2023 14:39:54 -0500 Subject: [PATCH 6/9] Exclude PHP 8.* & PHPUnit 8.* --- .github/workflows/acceptance.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.github/workflows/acceptance.yml b/.github/workflows/acceptance.yml index 8595ed6..dad778c 100644 --- a/.github/workflows/acceptance.yml +++ b/.github/workflows/acceptance.yml @@ -36,6 +36,15 @@ jobs: - php: "8.2" composer: 2 exclude: + - versions: + php: "8.0" + phpunit_version: 8 + - versions: + php: "8.1" + phpunit_version: 8 + - versions: + php: "8.2" + phpunit_version: 8 - versions: php: "7.2" phpunit_version: 9 From a88ec4533bf8cc1f336fe4d5cc729e70ed83f029 Mon Sep 17 00:00:00 2001 From: Ben Batschelet Date: Wed, 16 Aug 2023 14:59:54 -0500 Subject: [PATCH 7/9] Ignore PHPUnit 10 classes in 8 & 9, deprecate factory methods --- dev/phpunit8.neon | 1 + dev/phpunit9.neon | 1 + src/API/Value.php | 2 +- src/Constraint/Factory.php | 6 ++++++ 4 files changed, 9 insertions(+), 1 deletion(-) diff --git a/dev/phpunit8.neon b/dev/phpunit8.neon index fe4f6e1..2bd9dec 100644 --- a/dev/phpunit8.neon +++ b/dev/phpunit8.neon @@ -4,5 +4,6 @@ parameters: ignoreErrors: - '/PHPUnit\\Framework\\Constraint\\StringEqualsStringIgnoringLineEndings/' - '/PHPUnit\\Framework\\Constraint\\IsList/' + - '/PHPUnit\\Framework\\Constraint\\ObjectHasProperty/' - '/StringContains constructor invoked with 3 parameters, 1-2 required/' - '/deprecated class PHPUnit\\Framework\\Constraint\\TraversableContains/' diff --git a/dev/phpunit9.neon b/dev/phpunit9.neon index c23fefa..e53d66c 100644 --- a/dev/phpunit9.neon +++ b/dev/phpunit9.neon @@ -8,5 +8,6 @@ parameters: - '/PHPUnit\\Framework\\Constraint\\StringEqualsStringIgnoringLineEndings/' - '/PHPUnit\\Framework\\Constraint\\IsList/' + - '/PHPUnit\\Framework\\Constraint\\ObjectHasProperty/' - '/StringContains constructor invoked with 3 parameters, 1-2 required/' - '/PHPUnit\\Framework\\Constraint\\TraversableContains is abstract/' diff --git a/src/API/Value.php b/src/API/Value.php index 1d24217..725c794 100644 --- a/src/API/Value.php +++ b/src/API/Value.php @@ -484,7 +484,7 @@ final public function property(string $property): self try { $constraint = \is_string($this->getActualValue()) - ? $this->constraintFactory()->classHasAttribute($property) + ? $this->constraintFactory()->classHasAttribute($property) // @phpstan-ignore-line deprecated method : $this->constraintFactory()->objectHasProperty($property); } catch (InvalidConstraintException $e) { throw new BadMethodCallException('Asserting a class has a property is no longer supported in PHPUnit 10. You should update your test.', 0, $e); diff --git a/src/Constraint/Factory.php b/src/Constraint/Factory.php index 39d738f..78695d0 100644 --- a/src/Constraint/Factory.php +++ b/src/Constraint/Factory.php @@ -38,6 +38,9 @@ public function callback(callable $callback): PHPUnitConstraint\Callback return new PHPUnitConstraint\Callback($callback); } + /** + * @deprecated 3.2.0 + */ public function classHasAttribute(string $attribute): PHPUnitConstraint\ClassHasAttribute { $this->guardUnimplementedConstraint(PHPUnitConstraint\ClassHasAttribute::class); @@ -45,6 +48,9 @@ public function classHasAttribute(string $attribute): PHPUnitConstraint\ClassHas return new PHPUnitConstraint\ClassHasAttribute($attribute); } + /** + * @deprecated 3.2.0 + */ public function classHasStaticAttribute(string $attribute): PHPUnitConstraint\ClassHasStaticAttribute { $this->guardUnimplementedConstraint(PHPUnitConstraint\ClassHasStaticAttribute::class); From c49e8faeac93c1750da85b425180b770df0e947a Mon Sep 17 00:00:00 2001 From: Ben Batschelet Date: Thu, 17 Aug 2023 10:31:22 -0500 Subject: [PATCH 8/9] Fix PHPUnit 9 static tests --- dev/phpunit9.neon | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dev/phpunit9.neon b/dev/phpunit9.neon index e53d66c..9330c59 100644 --- a/dev/phpunit9.neon +++ b/dev/phpunit9.neon @@ -3,11 +3,12 @@ parameters: ignoreErrors: - - message: '/should return PHPUnit\\Framework\\Constraint\\Logical(And|Or) but returns PHPUnit\\Framework\\Constraint\\BinaryOperator/' + message: '/should return PHPUnit\\Framework\\Constraint\\Logical(And|Or|Xor) but returns PHPUnit\\Framework\\Constraint\\BinaryOperator/' path: %currentWorkingDirectory%/src/Constraint/Factory.php - '/PHPUnit\\Framework\\Constraint\\StringEqualsStringIgnoringLineEndings/' - '/PHPUnit\\Framework\\Constraint\\IsList/' - '/PHPUnit\\Framework\\Constraint\\ObjectHasProperty/' + - '/deprecated class PHPUnit\\Framework\\Constraint\\ObjectHasAttribute/' - '/StringContains constructor invoked with 3 parameters, 1-2 required/' - '/PHPUnit\\Framework\\Constraint\\TraversableContains is abstract/' From 005f96ef4e4bb75c0d05e0b5cf134e112d4618d7 Mon Sep 17 00:00:00 2001 From: Ben Batschelet Date: Thu, 17 Aug 2023 12:52:52 -0500 Subject: [PATCH 9/9] Add badge for PHPUnit, shorten job names --- .github/workflows/acceptance.yml | 2 +- README.md | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/acceptance.yml b/.github/workflows/acceptance.yml index dad778c..0e114a0 100644 --- a/.github/workflows/acceptance.yml +++ b/.github/workflows/acceptance.yml @@ -13,7 +13,7 @@ on: jobs: acceptance-tests: - name: Acceptance Tests PHP ${{ matrix.versions.php }} / PHPUnit ${{ matrix.phpunit_version }} + name: PHP ${{ matrix.versions.php }} / PHPUnit ${{ matrix.phpunit_version }} runs-on: ubuntu-latest strategy: diff --git a/README.md b/README.md index fe43273..d779f50 100644 --- a/README.md +++ b/README.md @@ -7,8 +7,9 @@ With BDD assertions influenced by Chai, Jasmine, and RSpec your assertions will [![Latest Stable Version](https://img.shields.io/packagist/v/bebat/verify.svg?style=flat-square)](https://packagist.org/packages/bebat/verify) [![Required PHP Version](https://img.shields.io/packagist/php-v/bebat/verify.svg?style=flat-square)](https://packagist.org/packages/bebat/verify) +[![Required PHPUnit Version](https://img.shields.io/packagist/dependency-v/bebat/verify/phpunit%2Fphpunit?style=flat-square)](https://packagist.org/packages/phpunit/phpunit) [![License](https://img.shields.io/packagist/l/bebat/verify?style=flat-square)](LICENSE) -[![Acceptance Test Status](https://img.shields.io/github/actions/workflow/status/bbatsche/Verify/acceptance.yml?branch=develop&style=flat-square)](https://github.com/bbatsche/Verify/actions/workflows/acceptance.yml) +[![Acceptance Test Status](https://img.shields.io/github/actions/workflow/status/bbatsche/Verify/acceptance.yml?branch=master&style=flat-square)](https://github.com/bbatsche/Verify/actions/workflows/acceptance.yml) [![Code Coverage](https://img.shields.io/codecov/c/github/bbatsche/Verify?style=flat-square)](https://codecov.io/gh/bbatsche/Verify) Most of the original work was done by [@DavertMik](http://github.com/DavertMik) and [@Ragazzo](http://github.com/Ragazzo) in the [Codeception/Verify](http://github.com/Codeception/Verify) repo. This version provides an alternate API and feature set, while sticking to the original BDD philosophy.