Skip to content

Commit f319edb

Browse files
authoredNov 16, 2022
Merge pull request #44 from Yoast/develop
Release version 1.1.0
·
1.2.01.1.0
2 parents 21df3a0 + 5872af5 commit f319edb

File tree

14 files changed

+815
-45
lines changed

14 files changed

+815
-45
lines changed
 

‎.github/dependabot.yml

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Dependabot configuration.
2+
#
3+
# Please see the documentation for all configuration options:
4+
# https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
5+
6+
version: 2
7+
updates:
8+
# Maintain dependencies for GitHub Actions.
9+
- package-ecosystem: "github-actions"
10+
directory: "/"
11+
schedule:
12+
interval: "weekly"
13+
open-pull-requests-limit: 5
14+
commit-message:
15+
prefix: "GH Actions:"
16+
labels:
17+
- "yoastcs/qa"
18+
19+
# Maintain dependencies for Composer.
20+
- package-ecosystem: "composer"
21+
directory: "/"
22+
schedule:
23+
interval: "weekly"
24+
open-pull-requests-limit: 5 # Set to 0 to (temporarily) disable.
25+
versioning-strategy: "increase-if-necessary"
26+
commit-message:
27+
prefix: "Composer:"
28+
labels:
29+
- "yoastcs/qa"

‎.github/workflows/cs.yml

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ jobs:
1414

1515
steps:
1616
- name: Checkout code
17-
uses: actions/checkout@v2
17+
uses: actions/checkout@v3
1818

1919
- name: Install PHP
2020
uses: shivammathur/setup-php@v2
@@ -26,7 +26,10 @@ jobs:
2626
# Install dependencies and handle caching in one go.
2727
# @link https://github.com/marketplace/actions/install-composer-dependencies
2828
- name: Install Composer dependencies
29-
uses: "ramsey/composer-install@v1"
29+
uses: "ramsey/composer-install@v2"
30+
with:
31+
# Bust the cache at least once a month - output format: YYYY-MM-DD.
32+
custom-cache-suffix: $(date -u -d "-0 month -$(($(date +%d)-1)) days" "+%F")
3033

3134
# Validate the composer.json file.
3235
# @link https://getcomposer.org/doc/03-cli.md#validate
@@ -35,8 +38,9 @@ jobs:
3538

3639
# Check the code-style consistency of the PHP files.
3740
- name: Check PHP code style
38-
continue-on-error: true
41+
id: phpcs
3942
run: composer check-cs -- --report-full --report-checkstyle=./phpcs-report.xml
4043

4144
- name: Show PHPCS results in PR
45+
if: ${{ always() && steps.phpcs.outcome == 'failure' }}
4246
run: cs2pr ./phpcs-report.xml

‎.github/workflows/test.yml

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -14,20 +14,13 @@ jobs:
1414

1515
strategy:
1616
matrix:
17-
php: ['5.6', '7.0', '7.1', '7.2', '7.3', '7.4', '8.0']
18-
experimental: [false]
19-
20-
include:
21-
- php: '8.1'
22-
experimental: true
17+
php: ['5.6', '7.0', '7.1', '7.2', '7.3', '7.4', '8.0', '8.1', '8.2']
2318

2419
name: "Tests: PHP ${{ matrix.php }}"
2520

26-
continue-on-error: ${{ matrix.experimental }}
27-
2821
steps:
2922
- name: Checkout code
30-
uses: actions/checkout@v2
23+
uses: actions/checkout@v3
3124

3225
- name: Install PHP
3326
uses: shivammathur/setup-php@v2
@@ -38,16 +31,11 @@ jobs:
3831

3932
# Install dependencies and handle caching in one go.
4033
# @link https://github.com/marketplace/actions/install-composer-dependencies
41-
- name: Install Composer dependencies for PHP < 8.1
42-
if: ${{ matrix.php < 8.1 }}
43-
uses: "ramsey/composer-install@v1"
44-
45-
# For PHP 8.1 and above, we need to install with ignore platform reqs as not all dependencies allow it yet.
46-
- name: Install Composer dependencies for PHP >= 8.1
47-
if: ${{ matrix.php >= 8.1 }}
48-
uses: "ramsey/composer-install@v1"
34+
- name: Install Composer dependencies
35+
uses: "ramsey/composer-install@v2"
4936
with:
50-
composer-options: --ignore-platform-reqs
37+
# Bust the cache at least once a month - output format: YYYY-MM-DD.
38+
custom-cache-suffix: $(date -u -d "-0 month -$(($(date +%d)-1)) days" "+%F")
5139

5240
- name: Lint PHP files against parse errors
5341
run: composer lint

‎.phpcs.xml.dist

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -109,12 +109,18 @@
109109

110110
<!-- Allow for camelCase method and variable names to be more in line with PHPUnit and BrainMonkey. -->
111111
<rule ref="WordPress.NamingConventions.ValidFunctionName">
112+
<exclude-pattern>/src/BrainMonkey/bootstrap\.php$</exclude-pattern>
112113
<exclude-pattern>/src/Helpers/*\.php$</exclude-pattern>
113114
</rule>
114115
<rule ref="WordPress.NamingConventions.ValidVariableName">
115116
<exclude-pattern>/src/Helpers/*\.php$</exclude-pattern>
116117
</rule>
117118

119+
<!-- This class is not a test double for the tests, but a _feature_ of this package. -->
120+
<rule ref="Yoast.Files.TestDoubles">
121+
<exclude-pattern>/src/BrainMonkey/Doubles/DummyTestDouble\.php$</exclude-pattern>
122+
</rule>
123+
118124
<!-- Ignore word count for object names in tests. -->
119125
<rule ref="Yoast.NamingConventions.ObjectNameDepth.MaxExceeded">
120126
<exclude-pattern>/tests/*\.php$</exclude-pattern>
@@ -125,16 +131,4 @@
125131
<exclude-pattern>/tests/*/Fixtures/*\.php$</exclude-pattern>
126132
</rule>
127133

128-
<!--
129-
#############################################################################
130-
TEMPORARY ADJUSTMENTS
131-
Adjustments which should be removed once the associated issue has been resolved.
132-
#############################################################################
133-
-->
134-
135-
<!-- PHPCS Bug: https://github.com/squizlabs/PHP_CodeSniffer/pull/3184 -->
136-
<rule ref="PSR2.Namespaces.NamespaceDeclaration">
137-
<exclude-pattern>/src/WPIntegration/(bootstrap-functions|Autoload)\.php$</exclude-pattern>
138-
</rule>
139-
140134
</ruleset>

‎CHANGELOG.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,28 @@ This projects adheres to [Keep a CHANGELOG](http://keepachangelog.com/) and uses
99

1010
_Nothing yet._
1111

12+
## [1.1.0] - 2022-11-17
13+
14+
### Added
15+
* Helper functions for on-the-fly creation of opaque test doubles for unavailable classes for use with Mockery. PR [#40]
16+
The default Mockery mocks for unavailable classes do not allow for the dynamic property deprecation in PHP 8.2, which can cause tests to error out.
17+
These helper functions can be used to create test doubles which do allow for setting properties.
18+
The helper functions can be called from within a test bootstrap or from within a test class.
19+
For full details about these new functions, why they exist and how to use them, please see [the documentation in the README](https://github.com/Yoast/wp-test-utils#helpers-to-create-test-doubles-for-unavailable-classes).
20+
21+
### Changed
22+
* The [BrainMonkey] dependency has been updated to require [version `^2.6.1`](https://github.com/Brain-WP/BrainMonkey/releases/tag/2.6.1) (was `^2.6.0`). PR [#28]
23+
* The [PHPUnit Polyfills] dependency has been updated to require [version `^1.0.4`](https://github.com/Yoast/PHPUnit-Polyfills/releases/tag/1.0.4) (was `^1.0.1`). PRs [#28], [#41]
24+
* Composer: The package will now identify itself as a testing tool. PR [#36]
25+
* Verified PHP 8.2 compatibility.
26+
* General housekeeping.
27+
28+
[#28]: https://github.com/Yoast/wp-test-utils/pull/28
29+
[#36]: https://github.com/Yoast/wp-test-utils/pull/36
30+
[#40]: https://github.com/Yoast/wp-test-utils/pull/40
31+
[#41]: https://github.com/Yoast/wp-test-utils/pull/41
32+
33+
1234
## [1.0.0] - 2021-09-27
1335

1436
WordPress 5.9 contains significant changes to the WordPress native test suite, which impacts **integration tests**.<br/>
@@ -86,6 +108,7 @@ Initial release.
86108

87109

88110
[Unreleased]: https://github.com/Yoast/wp-test-utils/compare/main...HEAD
111+
[1.1.0]: https://github.com/Yoast/wp-test-utils/compare/1.0.0...1.1.0
89112
[1.0.0]: https://github.com/Yoast/wp-test-utils/compare/0.2.2...1.0.0
90113
[0.2.2]: https://github.com/Yoast/wp-test-utils/compare/0.2.1...0.2.2
91114
[0.2.1]: https://github.com/Yoast/wp-test-utils/compare/0.2.0...0.2.1

‎README.md

Lines changed: 76 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ WP Test Utils
55
[![CS Build Status](https://github.com/Yoast/wp-test-utils/actions/workflows/cs.yml/badge.svg)](https://github.com/Yoast/wp-test-utils/actions/workflows/cs.yml)
66
[![Test Build Status](https://github.com/Yoast/wp-test-utils/actions/workflows/test.yml/badge.svg)](https://github.com/Yoast/wp-test-utils/actions/workflows/test.yml)
77
[![Minimum PHP Version](https://img.shields.io/packagist/php-v/yoast/wp-test-utils.svg?maxAge=3600)](https://packagist.org/packages/yoast/wp-test-utils)
8-
[![License: BSD3](https://poser.pugx.org/yoast/wp-test-utils/license)](https://github.com/Yoast/wp-test-utils/blob/master/LICENSE)
8+
[![License: BSD3](https://poser.pugx.org/yoast/wp-test-utils/license)](https://github.com/Yoast/wp-test-utils/blob/main/LICENSE)
99

1010
This library contains a set of utilities for running automated tests for WordPress plugins and themes.
1111

@@ -16,6 +16,7 @@ This library contains a set of utilities for running automated tests for WordPre
1616
- [Basic `TestCase` for use with BrainMonkey](#basic-testcase-for-use-with-brainmonkey)
1717
- [Yoast TestCase for use with BrainMonkey](#yoast-testcase-for-use-with-brainmonkey)
1818
- [Bootstrap file for use with BrainMonkey](#bootstrap-file-for-use-with-brainmonkey)
19+
- [Helpers to create test doubles for unavailable classes](#helpers-to-create-test-doubles-for-unavailable-classes)
1920
- [Utilities for running integration tests with WordPress](#utilities-for-running-integration-tests-with-wordpress)
2021
- [What these utilities solve](#what-these-utilities-solve)
2122
- [Basic `TestCase` for WordPress integration tests](#basic-testcase-for-wordpress-integration-tests)
@@ -32,9 +33,9 @@ Requirements
3233
* PHP 5.6 or higher.
3334

3435
The following packages will be automatically required via Composer:
35-
* [PHPUnit Polyfills] 1.0.1 or higher.
36+
* [PHPUnit Polyfills] 1.0.4 or higher.
3637
* [PHPUnit] 5.7 - 9.x.
37-
* [BrainMonkey] 2.6.0 or higher.
38+
* [BrainMonkey] 2.6.1 or higher.
3839

3940

4041
Installation
@@ -67,7 +68,8 @@ Features of this `TestCase`:
6768
The BrainMonkey native functions create stubs which will apply basic HTML escaping if the stubbed function is an escaping function, like `esc_html__()`.<br/>
6869
The alternative implementations of these functions will create stubs which will return the original value without change. This makes creating tests easier as the `$expected` value does not need to account for the HTML escaping.<br/>
6970
_Note: the alternative implementation should be used selectively._
70-
5. Helper functions for setting expectations for generated output.
71+
5. Helper functions for [setting expectations for generated output](#yoastwptestutilshelpersescapeoutputhelper-trait).
72+
6. Helper functions for [creating "dummy" test doubles for unavailable classes](#helpers-to-create-test-doubles-for-unavailable-classes).
7173

7274
**_Implementation example:_**
7375
```php
@@ -168,6 +170,76 @@ require_once dirname( __DIR__ ) . '/vendor/autoload.php';
168170
To tell PHPUnit to use this bootstrap file, use `--bootstrap tests/bootstrap.php` on the command-line or add the `bootstrap="tests/bootstrap.php"` attribute to your `phpunit.xml.dist` file.
169171

170172

173+
#### Helpers to create test doubles for unavailable classes
174+
175+
##### Why you may need to create test doubles for unavailable classes
176+
177+
Typically a mock for an unavailable class is created using `Mockery::mock()` or `Mockery::mock( 'Unavailable' )`.
178+
179+
When either the test or the code under test sets a property on such a mock, this will lead to a ["Creation of dynamic properties is deprecated" notice](https://wiki.php.net/rfc/deprecate_dynamic_properties) on PHP >= 8.2, which can cause tests to error out.
180+
181+
If you know for sure the property being set on the mock is a declared property or a property supported via [magic methods](https://www.php.net/oop5.overloading#language.oop5.overloading.members) on the class which is being mocked, the _code under test_ will under normal circumstances never lead to this deprecation notice, but your tests now do.
182+
183+
Primarly this is an issue with Mockery. A [question on how to handle this/to add support for this in Mockery itself](https://github.com/mockery/mockery/issues/1197) is open, but in the mean time a work-around is needed (if you're interested, have a read through the Mockery issue for details about alternative solutions and why those don't work as intended).
184+
185+
##### How to use the functionality
186+
187+
WP Test Utils offers three new utilities to solve this (as of version 1.1.0).
188+
* `Yoast\WPTestUtils\BrainMonkey\makeDoublesForUnavailableClasses( array $class_names )` for use from within a test bootstrap file.
189+
* `Yoast\WPTestUtils\BrainMonkey\TestCase::makeDoublesForUnavailableClasses( array $class_names )` and `Yoast\WPTestUtils\BrainMonkey\TestCase::makeDoubleForUnavailableClass( string $class_name )` for use from within a test class.
190+
191+
These methods can be used to create one or more "fake" test double classes on the fly, which allow for setting (dynamic) properties.
192+
These "fake" test double classes are effectively aliases for `stdClass`.
193+
194+
These methods are solely intended for classes which are unavailable during the test run and have no effect (at all!) on classes which _are_ available during the test run.
195+
196+
For setting expectations on the "fake" test double, use `Mockery::mock( 'FakedClass' )`.
197+
198+
**_Implementation example using the bootstrap function:_**
199+
200+
You can create the "fake" test doubles for a complete test suite in one go in your test bootstrap file like so:
201+
202+
```php
203+
<?php
204+
// File: tests/bootstrap.php
205+
require_once dirname( __DIR__ ) . '/vendor/yoast/wp-test-utils/src/BrainMonkey/bootstrap.php';
206+
require_once dirname( __DIR__ ) . '/vendor/autoload.php';
207+
208+
Yoast\WPTestUtils\BrainMonkey\makeDoublesForUnavailableClasses( [ 'WP_Post', 'wpdb' ] );
209+
```
210+
211+
**_Implementation example using these functions from within a test class:_**
212+
213+
Alternatively, you can create the "fake" test double(s) last minute in each test class which needs them.
214+
215+
```php
216+
<?php
217+
namespace PackageName\Tests;
218+
219+
use Mockery;
220+
use WP_Post;
221+
use wpdb;
222+
use Yoast\WPTestUtils\BrainMonkey\TestCase;
223+
224+
class FooTest extends TestCase {
225+
protected function set_up_before_class() {
226+
parent::set_up_before_class();
227+
self::makeDoublesForUnavailableClasses( [ WP_Post::class, wpdb::class ] );
228+
// or if only one class is needed:
229+
self::makeDoubleForUnavailableClass( WP_Post::class );
230+
}
231+
232+
public function testSomethingWhichUsesWpPost() {
233+
$wp_post = Mockery::mock( WP_Post::class );
234+
$wp_post->post_title = 'my test title';
235+
$wp_post->post_type = 'my_custom_type';
236+
237+
$this->assertSame( 'expected', \function_under_test( $wp_post ) );
238+
}
239+
}
240+
```
241+
242+
171243
### Utilities for running integration tests with WordPress
172244

173245
#### What these utilities solve

‎composer.json

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name" : "yoast/wp-test-utils",
33
"description" : "PHPUnit cross-version compatibility layer for testing plugins and themes build for WordPress",
4-
"keywords" : [ "wordpress", "unit-testing", "integration-testing", "brainmonkey", "phpunit" ],
4+
"keywords" : [ "wordpress", "unit-testing", "integration-testing", "brainmonkey", "phpunit", "testing" ],
55
"license" : "BSD-3-Clause",
66
"homepage": "https://github.com/Yoast/wp-test-utils/",
77
"authors": [
@@ -21,11 +21,16 @@
2121
},
2222
"require" : {
2323
"php" : ">=5.6",
24-
"yoast/phpunit-polyfills": "^1.0.1",
25-
"brain/monkey": "^2.6.0"
24+
"yoast/phpunit-polyfills": "^1.0.4",
25+
"brain/monkey": "^2.6.1"
2626
},
2727
"require-dev" : {
28-
"yoast/yoastcs": "^2.2.0"
28+
"yoast/yoastcs": "^2.2.1"
29+
},
30+
"config": {
31+
"allow-plugins": {
32+
"dealerdirect/phpcodesniffer-composer-installer": true
33+
}
2934
},
3035
"minimum-stability": "dev",
3136
"prefer-stable": true,
@@ -51,7 +56,7 @@
5156
},
5257
"scripts" : {
5358
"lint": [
54-
"@php ./vendor/php-parallel-lint/php-parallel-lint/parallel-lint . -e php --exclude vendor --exclude .git"
59+
"@php ./vendor/php-parallel-lint/php-parallel-lint/parallel-lint . -e php --show-deprecated --exclude vendor --exclude .git"
5560
],
5661
"check-cs": [
5762
"@php ./vendor/squizlabs/php_codesniffer/bin/phpcs"
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php
2+
3+
namespace Yoast\WPTestUtils\BrainMonkey\Doubles;
4+
5+
use stdClass;
6+
7+
/**
8+
* This is a "dummy" test double class for use with Mockery.
9+
*
10+
* This class allows to mock classes which are unavailable during the test run
11+
* and on which properties need to be set, either from within the test or
12+
* from within the code under test, by aliasing this class ahead of creating the mock.
13+
*
14+
* Mocking unavailable classes using an anonymous mock - `Mockery::mock()` or
15+
* a mock for a specific named, but unavailable class - `Mockery::mock( 'Unavailable' )` -
16+
* worked fine prior to PHP 8.2.
17+
* However, PHP 8.2 deprecates the use of dynamic properties, which means that if
18+
* either of the above code patterns is used and either the test or the code under
19+
* test sets properties on the Mock, tests will throw deprecation notices and,
20+
* depending on the value for the PHPUnit `convertDeprecationsToExceptions` configuration
21+
* option, tests may show as errored.
22+
*
23+
* The "go to" pattern to solve this is to let the mock extend `stdClass`, but
24+
* as `stdClass` always exists, the class will then identify as an instance of `stdClass`
25+
* and no longer as an instance of the "Unavailable" class, which causes problems
26+
* with code using type declarations of calls to `instanceof`.
27+
*
28+
* The other alternative would be to used `Mockery::namedMock()` or an alias mock, but
29+
* both of these require each test using these to run in a separate process, which
30+
* makes debugging of failing tests more complicated as well as making the test suite
31+
* slower.
32+
*
33+
* Note: aliasing `stdClass` directly is not allowed in PHP, which is why this
34+
* dummy test double class is put in place.
35+
*
36+
* @link https://github.com/mockery/mockery/issues/1197
37+
*/
38+
class DummyTestDouble extends stdClass {}

‎src/BrainMonkey/TestCase.php

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use Brain\Monkey\Functions;
77
use Mockery\Adapter\Phpunit\MockeryPHPUnitIntegration;
88
use Yoast\PHPUnitPolyfills\TestCases\TestCase as Polyfill_TestCase;
9+
use Yoast\WPTestUtils\BrainMonkey\Doubles\DummyTestDouble;
910
use Yoast\WPTestUtils\Helpers\ExpectOutputHelper;
1011

1112
/**
@@ -97,4 +98,54 @@ public function stubEscapeFunctions() {
9798
]
9899
);
99100
}
101+
102+
/**
103+
* On the fly create a "fake" test double class, which allows for setting
104+
* (dynamic) properties on it.
105+
*
106+
* This method is solely intended for classes which are unavailable during
107+
* the test run.
108+
*
109+
* Typically a mock for an unavailable class is created using `Mockery::mock()`
110+
* or `Mockery::mock( 'Unavailable' )`.
111+
* When either the test or the code under test sets a property on such a mock,
112+
* this will lead to a "Creation of dynamic properties is deprecated"
113+
* notice on PHP >= 8.2, which can cause tests to error out.
114+
*
115+
* This method provides a work-around for this by on the fly creating a test double
116+
* for the unavailable class which allows for setting dynamic properties.
117+
*
118+
* This method can be called during the test bootstrapping, in test `set_up()`
119+
* methods or in the test itself (also see the linked helper functions).
120+
*
121+
* For setting expectations on the "fake" test double, use `Mockery::mock( 'FakedClass' )`.
122+
*
123+
* @see Yoast\WPTestUtils\BrainMonkey\makeDoublesForUnavailableClasses() Create one or more fake doubles during the test bootstrapping.
124+
* @see Yoast\WPTestUtils\BrainMonkey\TestCase::makeDoublesForUnavailableClasses() Create one or more fake doubles in one go.
125+
*
126+
* @param string $class_name Name of the class to be "faked".
127+
*
128+
* @return void
129+
*/
130+
public static function makeDoubleForUnavailableClass( $class_name ) {
131+
if ( \class_exists( $class_name ) === false ) {
132+
\class_alias( DummyTestDouble::class, $class_name );
133+
}
134+
}
135+
136+
/**
137+
* On the fly create multiple "fake" test double classes which allow for setting
138+
* (dynamic) properties on them.
139+
*
140+
* @see TestCase::makeDoubleForUnavailableClass()
141+
*
142+
* @param string[] $class_names List of class names to be "faked".
143+
*
144+
* @return void
145+
*/
146+
public static function makeDoublesForUnavailableClasses( array $class_names ) {
147+
foreach ( $class_names as $class_name ) {
148+
self::makeDoubleForUnavailableClass( $class_name );
149+
}
150+
}
100151
}

‎src/BrainMonkey/bootstrap.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,17 @@
3232
if ( \function_exists( 'opcache_reset' ) ) {
3333
\opcache_reset();
3434
}
35+
36+
/**
37+
* On the fly create multiple "fake" test double classes which allow for setting
38+
* (dynamic) properties on them.
39+
*
40+
* @see TestCase::makeDoubleForUnavailableClass()
41+
*
42+
* @param string[] $class_names List of class names to be "faked".
43+
*
44+
* @return void
45+
*/
46+
function makeDoublesForUnavailableClasses( array $class_names ) {
47+
TestCase::makeDoublesForUnavailableClasses( $class_names );
48+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?php
2+
3+
namespace Yoast\WPTestUtils\Tests\BrainMonkey\Fixtures;
4+
5+
/**
6+
* Fixture to test the TestCase::makeDoubleForUnavailableClass() method.
7+
*/
8+
class AnotherAvailableClass {}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?php
2+
3+
namespace Yoast\WPTestUtils\Tests\BrainMonkey\Fixtures;
4+
5+
/**
6+
* Fixture to test the TestCase::makeDoubleForUnavailableClass() method.
7+
*/
8+
class AvailableClass {}

‎tests/BrainMonkey/TestCaseTest.php

Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,13 @@
33
namespace Yoast\WPTestUtils\Tests\BrainMonkey;
44

55
use Brain\Monkey\Functions;
6+
use Mockery;
7+
use ReflectionClass;
8+
use UnavailableClassB;
9+
use Yoast\WPTestUtils\BrainMonkey\Doubles\DummyTestDouble;
610
use Yoast\WPTestUtils\BrainMonkey\TestCase;
11+
use Yoast\WPTestUtils\Tests\BrainMonkey\Fixtures\AnotherAvailableClass;
12+
use Yoast\WPTestUtils\Tests\BrainMonkey\Fixtures\AvailableClass;
713

814
/**
915
* Basic test for the BrainMonkey TestCase setup.
@@ -54,6 +60,46 @@ public function testStubTranslationFunctions() {
5460
);
5561
}
5662

63+
/**
64+
* Verify the input dependent behaviour of the stub for the `_n()` function.
65+
*
66+
* @return void
67+
*/
68+
public function testStubTranslationFunctionsN() {
69+
$this->stubTranslationFunctions();
70+
71+
$this->assertSame(
72+
'chair',
73+
\_n( 'chair', 'chairs', 1 ),
74+
'Function stub for _n() does not return singular when number is 1'
75+
);
76+
$this->assertSame(
77+
'chairs',
78+
\_n( 'chair', 'chairs', 10 ),
79+
'Function stub for _n() does not return plural when number is not 1'
80+
);
81+
}
82+
83+
/**
84+
* Verify the input dependent behaviour of the stub for the `_nx()` function.
85+
*
86+
* @return void
87+
*/
88+
public function testStubTranslationFunctionsNx() {
89+
$this->stubTranslationFunctions();
90+
91+
$this->assertSame(
92+
'table',
93+
\_nx( 'table', 'tables', 1, 'test' ),
94+
'Function stub for _nx() does not return singular when number is 1'
95+
);
96+
$this->assertSame(
97+
'tables',
98+
\_nx( 'table', 'tables', 10, 'test' ),
99+
'Function stub for _nx() does not return plural when number is not 1'
100+
);
101+
}
102+
57103
/**
58104
* Verify the alternative translations function stubbing for functions echo-ing output is available
59105
* and that the functions echo out the input unchanged.
@@ -97,4 +143,170 @@ public function testStubEscapeFunctions() {
97143
\esc_html( 'some <div>test</div>' )
98144
);
99145
}
146+
147+
/**
148+
* Verify that creating a test double aliases for an unavailable class works as expected.
149+
*
150+
* @return void
151+
*/
152+
public function testMakeDoubleForUnavailableClass() {
153+
$this->assertFalse(
154+
\class_exists( 'UnavailableClassA' ),
155+
'Class UnavailableClassA appears to already exist'
156+
);
157+
158+
$this->makeDoubleForUnavailableClass( 'UnavailableClassA' );
159+
160+
$this->assertTrue(
161+
\class_exists( 'UnavailableClassA' ),
162+
'Class UnavailableClassA still doesn\'t appear to exist'
163+
);
164+
165+
$reflection_class = new ReflectionClass( 'UnavailableClassA' );
166+
$this->assertSame(
167+
DummyTestDouble::class,
168+
$reflection_class->getName(),
169+
'Class UnavailableClassA is not an alias for the DummyTestDouble class'
170+
);
171+
}
172+
173+
/**
174+
* Verify that properties can be set on a test double created using the `makeDoubleForUnavailableClass()` method.
175+
*
176+
* @return void
177+
*/
178+
public function testDoubleForUnavailableClassAllowsSettingProperties() {
179+
$this->assertFalse(
180+
\class_exists( UnavailableClassB::class ),
181+
'Class UnavailableClassB appears to already exist'
182+
);
183+
184+
static::makeDoubleForUnavailableClass( UnavailableClassB::class );
185+
186+
$unavailable_class = new UnavailableClassB();
187+
$unavailable_class->property = 10;
188+
189+
$this->assertTrue(
190+
\property_exists( $unavailable_class, 'property' ),
191+
'Property does not exist on test double'
192+
);
193+
$this->assertSame(
194+
10,
195+
$unavailable_class->property,
196+
'Property value on test double does not match expected value'
197+
);
198+
}
199+
200+
/**
201+
* Verify that properties can be set on a mock of a test double, which was created
202+
* using the `makeDoubleForUnavailableClass()` method.
203+
*
204+
* @return void
205+
*/
206+
public function testDoubleForUnavailableClassAllowsSettingPropertiesWhenMocked() {
207+
$this->assertFalse(
208+
\class_exists( 'UnavailableClassC' ),
209+
'Class UnavailableClassC appears to already exist'
210+
);
211+
212+
self::makeDoubleForUnavailableClass( 'UnavailableClassC' );
213+
214+
$mock_of_unavailable_class = Mockery::mock( 'UnavailableClassC' );
215+
$mock_of_unavailable_class->property = 'test';
216+
217+
$this->assertTrue(
218+
\property_exists( $mock_of_unavailable_class, 'property' ),
219+
'Property does not exist on mocked test double'
220+
);
221+
$this->assertSame(
222+
'test',
223+
$mock_of_unavailable_class->property,
224+
'Property value on mocked test double does not match expected value'
225+
);
226+
}
227+
228+
/**
229+
* Verify that no errors or warnings are thrown when a test double is requested for a class
230+
* which already exists, but wasn't loaded prior to the `makeDoubleForUnavailableClass()` function being called.
231+
*
232+
* @return void
233+
*/
234+
public function testMakeDoubleForAvailableClassNotYetInMemoryDoesNotCreateDouble() {
235+
$this->assertFalse(
236+
\class_exists( AvailableClass::class, false ), // Don't autoload this class!
237+
'Class Yoast\WPTestUtils\Tests\BrainMonkey\Fixtures\AvailableClass already loaded, test is invalid'
238+
);
239+
240+
$this->makeDoubleForUnavailableClass( AvailableClass::class );
241+
242+
$reflection_class = new ReflectionClass( AvailableClass::class );
243+
$this->assertSame(
244+
AvailableClass::class,
245+
$reflection_class->getName(),
246+
'The class does not point to the original, available class'
247+
);
248+
}
249+
250+
/**
251+
* Verify that no errors or warnings are thrown when a test double is requested for a class
252+
* which already exists and was already loaded prior to the `makeDoubleForUnavailableClass()` function being called.
253+
*
254+
* @return void
255+
*/
256+
public function testMakeDoubleForAvailableClassAlreadyInMemoryDoesNotCreateDouble() {
257+
// Don't load via autoloader.
258+
require_once __DIR__ . '/Fixtures/AnotherAvailableClass.php';
259+
260+
$this->assertTrue(
261+
\class_exists( AnotherAvailableClass::class ),
262+
'Class Yoast\WPTestUtils\Tests\BrainMonkey\Fixtures\AnotherAvailableClass doesn\'t exist prior to this test'
263+
);
264+
265+
$this->makeDoubleForUnavailableClass( AnotherAvailableClass::class );
266+
267+
$reflection_class = new ReflectionClass( AnotherAvailableClass::class );
268+
$this->assertSame(
269+
AnotherAvailableClass::class,
270+
$reflection_class->getName(),
271+
'The class does not point to the original, available class'
272+
);
273+
}
274+
275+
/**
276+
* Verify that creating multiple test double aliases in one go works as expected.
277+
*
278+
* This test also safeguards that the functionality also works with namespaced class names.
279+
*
280+
* @return void
281+
*/
282+
public function testMakeDoublesForUnavailableClasses() {
283+
$classes = [
284+
'UnavailableClassX',
285+
'My\\Namespace\\UnavailableClassY',
286+
'Other\\UnavailableClassZ',
287+
];
288+
289+
foreach ( $classes as $class_name ) {
290+
$this->assertFalse(
291+
\class_exists( $class_name ),
292+
"Class $class_name appears to already exist"
293+
);
294+
}
295+
296+
self::makeDoublesForUnavailableClasses( $classes );
297+
298+
foreach ( $classes as $class_name ) {
299+
$this->assertTrue(
300+
\class_exists( $class_name ),
301+
"Class $class_name still doesn't appear to exist"
302+
);
303+
304+
$reflection_class = new ReflectionClass( $class_name );
305+
$this->assertSame(
306+
DummyTestDouble::class,
307+
$reflection_class->getName(),
308+
"Class $class_name is not an alias for the DummyTestDouble class"
309+
);
310+
}
311+
}
100312
}

‎tests/BrainMonkey/YoastTestCaseTest.php

Lines changed: 327 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,336 @@
1212
class YoastTestCaseTest extends YoastTestCase {
1313

1414
/**
15-
* Verify that the additional stubs added in the setUp() are available.
15+
* Verify the behaviour of the `get_bloginfo()` stub.
16+
*
17+
* @dataProvider dataStubGetBlogInfo
18+
*
19+
* @param string $show Value to pass to the function.
20+
* @param string $expected Expected return value.
21+
*
22+
* @return void
23+
*/
24+
public function testStubGetBlogInfo( $show, $expected ) {
25+
$this->assertSame( $expected, \get_bloginfo( $show ) );
26+
}
27+
28+
/**
29+
* Data provider.
30+
*
31+
* @return array
32+
*/
33+
public function dataStubGetBlogInfo() {
34+
return [
35+
// Explicit cases.
36+
'charset' => [
37+
'show' => 'charset',
38+
'expected' => 'UTF-8',
39+
],
40+
'language' => [
41+
'show' => 'language',
42+
'expected' => 'English',
43+
],
44+
// Default behaviour, return input unchanged.
45+
'name' => [
46+
'show' => 'name',
47+
'expected' => 'name',
48+
],
49+
'pingback_url' => [
50+
'show' => 'pingback_url',
51+
'expected' => 'pingback_url',
52+
],
53+
];
54+
}
55+
56+
/**
57+
* Verify the behaviour of the `is_multisite()` stub, which should always return
58+
* false, except when the `WP_TESTS_MULTISITE` constant has been defined (which we're not testing).
1659
*
1760
* @return void
1861
*/
19-
public function testSetUp() {
20-
$this->assertSame( 'https://www.example.org', \site_url() );
62+
public function testStubIsMultisite() {
2163
$this->assertFalse( \is_multisite() );
2264
}
65+
66+
/**
67+
* Verify the behaviour of the `mysql2date()` stub, which should ignore the $format parameter completely.
68+
*
69+
* @return void
70+
*/
71+
public function testStubMysql2Date() {
72+
$date = '2022-11-16 00:25:41';
73+
$this->assertSame( $date, \mysql2date( 'U', $date ) );
74+
}
75+
76+
/**
77+
* Verify the behaviour of the `number_format_i18n()` stub, which should return
78+
* the first parameter passed unchanged.
79+
*
80+
* @return void
81+
*/
82+
public function testStubNumberFormatI18n() {
83+
$number = 123e7;
84+
$this->assertSame( $number, \number_format_i18n( $number, 5 ) );
85+
}
86+
87+
/**
88+
* Verify the behaviour of the `sanitize_text_field()` stub, which should return
89+
* the first parameter passed unchanged.
90+
*
91+
* @return void
92+
*/
93+
public function testStubSanitizeTextField() {
94+
$text = 'some text < which <span> needs sanitization ';
95+
$this->assertSame( $text, \sanitize_text_field( $text ) );
96+
}
97+
98+
/**
99+
* Verify the behaviour of the `site_url()` stub, which should always return
100+
* the same value regardless of the passed parameters.
101+
*
102+
* @return void
103+
*/
104+
public function testStubSiteUrl() {
105+
$this->assertSame( 'https://www.example.org', \site_url( 'some/path', 'rest' ) );
106+
}
107+
108+
/**
109+
* Verify the behaviour of the `wp_kses_post()` stub, which should return
110+
* the first parameter passed unchanged.
111+
*
112+
* @return void
113+
*/
114+
public function testStubWpKsesPost() {
115+
$text = 'some text < which <span> needs sanitization ';
116+
$this->assertSame( $text, \wp_kses_post( $text ) );
117+
}
118+
119+
/**
120+
* Verify the behaviour of the `wp_parse_args()` stub, which should return
121+
* a merged array.
122+
*
123+
* Note: not testing invalid input handling as these are function stubs to be used in tests,
124+
* so invalid input throwing errors is exactly the right behaviour.
125+
*
126+
* @dataProvider dataStubWpParseArgs
127+
*
128+
* @param array $settings Value for settings to pass to the function.
129+
* @param array $defaults Value for defaults to pass to the function.
130+
* @param array $expected Expected return value.
131+
*
132+
* @return void
133+
*/
134+
public function testStubWpParseArgs( $settings, $defaults, $expected ) {
135+
$this->assertSame( $expected, \wp_parse_args( $settings, $defaults ) );
136+
}
137+
138+
/**
139+
* Data provider.
140+
*
141+
* @return array
142+
*/
143+
public function dataStubWpParseArgs() {
144+
return [
145+
'two empty arrays' => [
146+
'settings' => [],
147+
'defaults' => [],
148+
'expected' => [],
149+
],
150+
'settings array empty, defaults array has values' => [
151+
'settings' => [
152+
'setting A' => true,
153+
'setting B' => 'string',
154+
'setting C' => 10,
155+
],
156+
'defaults' => [],
157+
'expected' => [
158+
'setting A' => true,
159+
'setting B' => 'string',
160+
'setting C' => 10,
161+
],
162+
],
163+
'settings array has values, defaults array empty' => [
164+
'settings' => [],
165+
'defaults' => [
166+
'setting A' => true,
167+
'setting B' => 'string',
168+
'setting C' => 10,
169+
],
170+
'expected' => [
171+
'setting A' => true,
172+
'setting B' => 'string',
173+
'setting C' => 10,
174+
],
175+
],
176+
'both arrays have values' => [
177+
'settings' => [
178+
'setting A' => true,
179+
'setting B' => 'string',
180+
'setting C' => 10,
181+
'setting E' => 'world!',
182+
],
183+
'defaults' => [
184+
'setting A' => false,
185+
'setting B' => 'default',
186+
'setting C' => 0,
187+
'setting D' => 'hello!',
188+
],
189+
'expected' => [
190+
'setting A' => true,
191+
'setting B' => 'string',
192+
'setting C' => 10,
193+
'setting D' => 'hello!',
194+
'setting E' => 'world!',
195+
],
196+
],
197+
];
198+
}
199+
200+
/**
201+
* Verify the behaviour of the `wp_strip_all_tags()` stub, which should return
202+
* a merged array.
203+
*
204+
* Note: not testing invalid input handling as these are function stubs to be used in tests,
205+
* so invalid input throwing errors is exactly the right behaviour.
206+
*
207+
* @dataProvider dataStubWpStripAllTags
208+
*
209+
* @param string $text Text to strip tags from.
210+
* @param bool $remove_breaks Whether or not to strip line breaks and tabs.
211+
* @param string $expected Expected return value.
212+
*
213+
* @return void
214+
*/
215+
public function testStubWpStripAllTags( $text, $remove_breaks, $expected ) {
216+
$this->assertSame( $expected, \wp_strip_all_tags( $text, $remove_breaks ) );
217+
}
218+
219+
/**
220+
* Data provider.
221+
*
222+
* @return array
223+
*/
224+
public function dataStubWpStripAllTags() {
225+
return [
226+
'Empty string' => [
227+
'text' => '',
228+
'remove_breaks' => true,
229+
'expected' => '',
230+
],
231+
'Text containing script tag; no remove breaks' => [
232+
// phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedScript -- Not relevant.
233+
'text' => '<script src="https://maliciousurl.com/">Click me
234+
</script>',
235+
'remove_breaks' => false,
236+
'expected' => '',
237+
],
238+
'Text containing other HTML tags; no remove breaks' => [
239+
'text' => '<span class="foo">my text</span>',
240+
'remove_breaks' => false,
241+
'expected' => 'my text',
242+
],
243+
'Text surrounded by whitespace; no remove breaks' => [
244+
'text' => ' my text
245+
',
246+
'remove_breaks' => false,
247+
'expected' => 'my text',
248+
],
249+
'Text containing line breaks and tabs; no remove breaks' => [
250+
'text' => 'my
251+
text
252+
and more',
253+
'remove_breaks' => false,
254+
'expected' => 'my
255+
text
256+
and more',
257+
],
258+
'Text containing line breaks and tabs; with remove breaks' => [
259+
'text' => 'my
260+
text
261+
and more',
262+
'remove_breaks' => true,
263+
'expected' => 'my text and more',
264+
],
265+
];
266+
}
267+
268+
/**
269+
* Verify the behaviour of the `wp_slash()` stub, which should return
270+
* the first parameter passed unchanged.
271+
*
272+
* @dataProvider dataStubWpSlash
273+
*
274+
* @param string $input Value to pass to the function.
275+
*
276+
* @return void
277+
*/
278+
public function testStubWpSlash( $input ) {
279+
$this->assertSame( $input, \wp_slash( $input ) );
280+
}
281+
282+
/**
283+
* Data provider.
284+
*
285+
* @return array
286+
*/
287+
public function dataStubWpSlash() {
288+
return [
289+
'string' => [
290+
'input' => "O'Reilly?",
291+
],
292+
'array' => [
293+
'input' => [
294+
"O'Reilly?",
295+
"aa\'bb",
296+
],
297+
],
298+
'invalid input: null' => [
299+
'input' => null,
300+
],
301+
];
302+
}
303+
304+
/**
305+
* Verify the behaviour of the `wp_unslash()` stub, which should return
306+
* the first parameter without slashes if a string or the first parameter
307+
* unchanged if a non-string input was received.
308+
*
309+
* @dataProvider dataStubWpUnslash
310+
*
311+
* @param string $input Value to pass to the function.
312+
* @param string $expected Expected return value.
313+
*
314+
* @return void
315+
*/
316+
public function testStubWpUnslash( $input, $expected ) {
317+
$this->assertSame( $expected, \wp_unslash( $input ) );
318+
}
319+
320+
/**
321+
* Data provider.
322+
*
323+
* @return array
324+
*/
325+
public function dataStubWpUnslash() {
326+
return [
327+
'string' => [
328+
'input' => "O\'Reilly\?",
329+
'expected' => "O'Reilly?",
330+
],
331+
'array' => [
332+
'input' => [
333+
"O\'Reilly?",
334+
"aa\'bb",
335+
],
336+
'expected' => [
337+
"O\'Reilly?",
338+
"aa\'bb",
339+
],
340+
],
341+
'invalid input: null' => [
342+
'input' => null,
343+
'expected' => null,
344+
],
345+
];
346+
}
23347
}

0 commit comments

Comments
 (0)
Please sign in to comment.