From ce2619f4eeb4d129c71a07f0dc0bd26e66afd0eb Mon Sep 17 00:00:00 2001
From: jrfnl <jrfnl@users.noreply.github.com>
Date: Mon, 6 Jan 2025 01:20:18 +0100
Subject: [PATCH] PHPUnit 11.5.0 | AssertContainsOnly trait: polyfill the
 Assert::assertContains[Not]Only*() methods

PHPUnit 11.5.0 introduced a range of new assertions. These assertions are specialized alternatives to the pre-existing `assert[Not]ContainsOnly()` methods, which are now soft deprecated and will be removed in PHPUnit 13.0.

This commit:
* Adds two traits with the same name.
    One to polyfill the methods when not available in PHPUnit.
    The other to allow for `use`-ing the trait in PHPUnit versions in which the methods are already natively available.
* Adds logic to the custom autoloader which will load the correct trait depending on the PHPUnit version used.
* Adds tests.

Most polyfilled methods can just fall-through to the pre-existing methods as a polyfill.
The exceptions to this rule are:
* `assertContains[Not]OnlyIterable()` which needs custom logic for PHPUnit < 7.1.0 in which the PHP native `iterable` type was not yet supported.
* `assertContains[Not]OnlyClosedResource()` which needs custom logic for PHP < 7.2 in which the PHP native `resource (closed)` type did not exist yet.
    In this last case, the custom logic is used for the polyfill on all PHP versions as there is no functional check (in contrast to a version check) which can be used to determine whether the `resource (closed)` type is available.

All new methods are loosely tested.

Includes:
* Adding information on the new polyfill to the README.
* Adding the new polyfill to the existing `TestCases` classes.

Refs:
* https://github.com/sebastianbergmann/phpunit/commit/a726e0396e71cc77bc0b459f93481c29e726dbd8

Fixes 214
---
 README.md                                  |   53 +
 phpstan.neon.dist                          |    1 +
 phpunitpolyfills-autoload.php              |   21 +
 src/Polyfills/AssertContainsOnly.php       |  472 ++++++++
 src/Polyfills/AssertContainsOnly_Empty.php |   10 +
 src/TestCases/TestCasePHPUnitGte8.php      |    2 +
 src/TestCases/TestCasePHPUnitLte7.php      |    2 +
 src/TestCases/XTestCase.php                |    2 +
 tests/Polyfills/AssertContainsOnlyTest.php | 1243 ++++++++++++++++++++
 tests/TestCases/TestCaseTestTrait.php      |    9 +
 10 files changed, 1815 insertions(+)
 create mode 100644 src/Polyfills/AssertContainsOnly.php
 create mode 100644 src/Polyfills/AssertContainsOnly_Empty.php
 create mode 100644 tests/Polyfills/AssertContainsOnlyTest.php

diff --git a/README.md b/README.md
index 8869104..d455cb7 100644
--- a/README.md
+++ b/README.md
@@ -496,6 +496,59 @@ The `assertObjectNotEquals()` assertion was introduced in PHPUnit 11.2.0.
 
 [`Assert::assertObjectNotEquals()`]: https://docs.phpunit.de/en/11.5/assertions.html#assertobjectequals
 
+#### PHPUnit < 11.5.0: `Yoast\PHPUnitPolyfills\Polyfills\AssertContainsOnly`
+
+Polyfills the following methods:
+
+|                                                  |                                                     |
+| ------------------------------------------------ | --------------------------------------------------- |
+| [`Assert::assertContainsOnlyArray()`]            | [`Assert::assertContainsNotOnlyArray()`]            |
+| [`Assert::assertContainsOnlyBool()`]             | [`Assert::assertContainsNotOnlyBool()`]             |
+| [`Assert::assertContainsOnlyCallable()`]         | [`Assert::assertContainsNotOnlyCallable()`]         |
+| [`Assert::assertContainsOnlyFloat()`]            | [`Assert::assertContainsNotOnlyFloat()`]            |
+| [`Assert::assertContainsOnlyInt()`]              | [`Assert::assertContainsNotOnlyInt()`]              |
+| [`Assert::assertContainsOnlyIterable()`]         | [`Assert::assertContainsNotOnlyIterable()`]         |
+| [`Assert::assertContainsOnlyNull()`]             | [`Assert::assertContainsNotOnlyNull()`]             |
+| [`Assert::assertContainsOnlyNumeric()`]          | [`Assert::assertContainsNotOnlyNumeric()`]          |
+| [`Assert::assertContainsOnlyObject()`]           | [`Assert::assertContainsNotOnlyObject()`]           |
+| [`Assert::assertContainsOnlyResource()`]         | [`Assert::assertContainsNotOnlyResource()`]         |
+| [`Assert::assertContainsOnlyClosedResource()`] * | [`Assert::assertContainsNotOnlyClosedResource()`] * |
+| [`Assert::assertContainsOnlyScalar()`]           | [`Assert::assertContainsNotOnlyScalar()`]           |
+| [`Assert::assertContainsOnlyString()`]           | [`Assert::assertContainsNotOnlyString()`]           |
+| [`Assert::assertContainsNotOnlyInstancesOf()`]   |                                                     |
+
+These methods were introduced in PHPUnit 11.5.0 as alternatives to the `Assert::assertContainsOnly()` and `Assert::assertNotContainsOnly()` methods, which were soft deprecated in PHPUnit 11.5.0, hard deprecated (warning) in PHPUnit 12.0.0 and will be removed in PHPUnit 13.0.0.
+
+* The `assertContains[Not]OnlyClosedResource()` methods are affected by issues in older PHP versions. Please read the [warning about checking for closed resources and how to optional skip such tests](https://github.com/Yoast/PHPUnit-Polyfills/tree/1.x?tab=readme-ov-file#phpunit--930-yoastphpunitpolyfillspolyfillsassertclosedresource).
+
+[`Assert::assertContainsOnlyArray()`]:             https://docs.phpunit.de/en/11.5/assertions.html#assertcontainsonlyarray
+[`Assert::assertContainsNotOnlyArray()`]:          https://docs.phpunit.de/en/11.5/assertions.html#assertcontainsonlyarray
+[`Assert::assertContainsOnlyBool()`]:              https://docs.phpunit.de/en/11.5/assertions.html#assertcontainsonlybool
+[`Assert::assertContainsNotOnlyBool()`]:           https://docs.phpunit.de/en/11.5/assertions.html#assertcontainsonlybool
+[`Assert::assertContainsOnlyCallable()`]:          https://docs.phpunit.de/en/11.5/assertions.html#assertcontainsonlycallable
+[`Assert::assertContainsNotOnlyCallable()`]:       https://docs.phpunit.de/en/11.5/assertions.html#assertcontainsonlycallable
+[`Assert::assertContainsOnlyFloat()`]:             https://docs.phpunit.de/en/11.5/assertions.html#assertcontainsonlyfloat
+[`Assert::assertContainsNotOnlyFloat()`]:          https://docs.phpunit.de/en/11.5/assertions.html#assertcontainsonlyfloat
+[`Assert::assertContainsOnlyInt()`]:               https://docs.phpunit.de/en/11.5/assertions.html#assertcontainsonlyint
+[`Assert::assertContainsNotOnlyInt()`]:            https://docs.phpunit.de/en/11.5/assertions.html#assertcontainsonlyint
+[`Assert::assertContainsOnlyIterable()`]:          https://docs.phpunit.de/en/11.5/assertions.html#assertcontainsonlyiterable
+[`Assert::assertContainsNotOnlyIterable()`]:       https://docs.phpunit.de/en/11.5/assertions.html#assertcontainsonlyiterable
+[`Assert::assertContainsOnlyNull()`]:              https://docs.phpunit.de/en/11.5/assertions.html#assertcontainsonlynull
+[`Assert::assertContainsNotOnlyNull()`]:           https://docs.phpunit.de/en/11.5/assertions.html#assertcontainsonlynull
+[`Assert::assertContainsOnlyNumeric()`]:           https://docs.phpunit.de/en/11.5/assertions.html#assertcontainsonlynumeric
+[`Assert::assertContainsNotOnlyNumeric()`]:        https://docs.phpunit.de/en/11.5/assertions.html#assertcontainsonlynumeric
+[`Assert::assertContainsOnlyObject()`]:            https://docs.phpunit.de/en/11.5/assertions.html#assertcontainsonlyobject
+[`Assert::assertContainsNotOnlyObject()`]:         https://docs.phpunit.de/en/11.5/assertions.html#assertcontainsonlyobject
+[`Assert::assertContainsOnlyResource()`]:          https://docs.phpunit.de/en/11.5/assertions.html#assertcontainsonlyresource
+[`Assert::assertContainsNotOnlyResource()`]:       https://docs.phpunit.de/en/11.5/assertions.html#assertcontainsonlyresource
+[`Assert::assertContainsOnlyClosedResource()`]:    https://docs.phpunit.de/en/11.5/assertions.html#assertcontainsonlyclosedresource
+[`Assert::assertContainsNotOnlyClosedResource()`]: https://docs.phpunit.de/en/11.5/assertions.html#assertcontainsonlyclosedresource
+[`Assert::assertContainsOnlyScalar()`]:            https://docs.phpunit.de/en/11.5/assertions.html#assertcontainsonlyscalar
+[`Assert::assertContainsNotOnlyScalar()`]:         https://docs.phpunit.de/en/11.5/assertions.html#assertcontainsonlyscalar
+[`Assert::assertContainsOnlyString()`]:            https://docs.phpunit.de/en/11.5/assertions.html#assertcontainsonlystring
+[`Assert::assertContainsNotOnlyString()`]:         https://docs.phpunit.de/en/11.5/assertions.html#assertcontainsonlystring
+[`Assert::assertContainsNotOnlyInstancesOf()`]:    https://docs.phpunit.de/en/11.5/assertions.html#assertcontainsonlyinstancesof
+
 
 ### TestCases
 
diff --git a/phpstan.neon.dist b/phpstan.neon.dist
index 5e02295..20b9dea 100644
--- a/phpstan.neon.dist
+++ b/phpstan.neon.dist
@@ -15,6 +15,7 @@ parameters:
 		- src/TestCases/TestCasePHPUnitLte7.php
 		# Triggers "Referencing prefixed PHPUnit class: PHPUnitPHAR\SebastianBergmann\Exporter\Exporter." notices, which cannot be ignored.
 		- src/Polyfills/AssertClosedResource.php
+		- src/Polyfills/AssertContainsOnly.php
 		- src/Polyfills/AssertIgnoringLineEndings.php
 	treatPhpDocTypesAsCertain: false
 
diff --git a/phpunitpolyfills-autoload.php b/phpunitpolyfills-autoload.php
index 265bb61..e8a45ee 100644
--- a/phpunitpolyfills-autoload.php
+++ b/phpunitpolyfills-autoload.php
@@ -98,6 +98,10 @@ public static function load( $className ) {
 					self::loadAssertObjectNotEquals();
 					return true;
 
+				case 'Yoast\PHPUnitPolyfills\Polyfills\AssertContainsOnly':
+					self::loadAssertContainsOnly();
+					return true;
+
 				case 'Yoast\PHPUnitPolyfills\TestCases\TestCase':
 					self::loadTestCase();
 					return true;
@@ -381,6 +385,23 @@ public static function loadAssertObjectNotEquals() {
 			require_once __DIR__ . '/src/Polyfills/AssertObjectNotEquals_Empty.php';
 		}
 
+		/**
+		 * Load the AssertContainsOnly polyfill or an empty trait with the same name
+		 * if a PHPUnit version is used which already contains this functionality.
+		 *
+		 * @return void
+		 */
+		public static function loadAssertContainsOnly() {
+			if ( \method_exists( Assert::class, 'assertContainsOnlyIterable' ) === false ) {
+				// PHPUnit < 11.5.0.
+				require_once __DIR__ . '/src/Polyfills/AssertContainsOnly.php';
+				return;
+			}
+
+			// PHPUnit >= 11.5.0.
+			require_once __DIR__ . '/src/Polyfills/AssertContainsOnly_Empty.php';
+		}
+
 		/**
 		 * Load the appropriate TestCase class based on the PHPUnit version being used.
 		 *
diff --git a/src/Polyfills/AssertContainsOnly.php b/src/Polyfills/AssertContainsOnly.php
new file mode 100644
index 0000000..a05a1c4
--- /dev/null
+++ b/src/Polyfills/AssertContainsOnly.php
@@ -0,0 +1,472 @@
+<?php
+
+namespace Yoast\PHPUnitPolyfills\Polyfills;
+
+use PHPUnit\SebastianBergmann\Exporter\Exporter as Exporter_In_Phar_Old;
+use PHPUnitPHAR\SebastianBergmann\Exporter\Exporter as Exporter_In_Phar;
+use SebastianBergmann\Exporter\Exporter;
+use Traversable;
+use Yoast\PHPUnitPolyfills\Autoload;
+use Yoast\PHPUnitPolyfills\Helpers\ResourceHelper;
+
+/**
+ * Polyfill the assertContainsNotOnlyInstancesOf(), assertContainsOnlyArray(), assertContainsOnlyBool(),
+ * assertContainsOnlyCallable(), assertContainsOnlyFloat(), assertContainsOnlyInt(),
+ * assertContainsOnlyIterable(), assertContainsOnlyNull(), assertContainsOnlyNumeric(),
+ * assertContainsOnlyObject(), assertContainsOnlyResource(), assertContainsOnlyClosedResource(),
+ * assertContainsOnlyScalar(), assertContainsOnlyString(), assertContainsNotOnlyArray(),
+ * assertContainsNotOnlyBool(), assertContainsNotOnlyCallable(), assertContainsNotOnlyFloat(),
+ * assertContainsNotOnlyInt(), assertContainsNotOnlyIterable(), assertContainsNotOnlyNull(),
+ * assertContainsNotOnlyNumeric(), assertContainsNotOnlyObject(), assertContainsNotOnlyResource(),
+ * assertContainsNotOnlyClosedResource(), assertContainsNotOnlyScalar(), and assertContainsNotOnlyString() methods.
+ *
+ * Introduced in PHPUnit 11.5.0.
+ *
+ * @link https://github.com/sebastianbergmann/phpunit/commit/a726e0396e71cc77bc0b459f93481c29e726dbd8
+ *
+ * @since 3.1.0
+ */
+trait AssertContainsOnly {
+
+	/**
+	 * Asserts that $haystack does not only contain instances of class or interface $type.
+	 *
+	 * @param string          $type     Class or interface name.
+	 * @param iterable<mixed> $haystack The variable to test.
+	 * @param string          $message  Optional failure message to display.
+	 *
+	 * @return void
+	 */
+	final public static function assertContainsNotOnlyInstancesOf( string $type, $haystack, string $message = '' ) {
+		static::assertNotContainsOnly( $type, $haystack, false, $message );
+	}
+
+	/**
+	 * Asserts that $haystack only contains values of type array.
+	 *
+	 * @param iterable<mixed> $haystack The variable to test.
+	 * @param string          $message  Optional failure message to display.
+	 *
+	 * @return void
+	 */
+	final public static function assertContainsOnlyArray( $haystack, string $message = '' ) {
+		static::assertContainsOnly( 'array', $haystack, true, $message );
+	}
+
+	/**
+	 * Asserts that $haystack does not only contain values of type array.
+	 *
+	 * @param iterable<mixed> $haystack The variable to test.
+	 * @param string          $message  Optional failure message to display.
+	 *
+	 * @return void
+	 */
+	final public static function assertContainsNotOnlyArray( $haystack, string $message = '' ) {
+		static::assertNotContainsOnly( 'array', $haystack, true, $message );
+	}
+
+	/**
+	 * Asserts that $haystack only contains values of type boolean.
+	 *
+	 * @param iterable<mixed> $haystack The variable to test.
+	 * @param string          $message  Optional failure message to display.
+	 *
+	 * @return void
+	 */
+	final public static function assertContainsOnlyBool( $haystack, string $message = '' ) {
+		static::assertContainsOnly( 'bool', $haystack, true, $message );
+	}
+
+	/**
+	 * Asserts that $haystack does not only contain values of type boolean.
+	 *
+	 * @param iterable<mixed> $haystack The variable to test.
+	 * @param string          $message  Optional failure message to display.
+	 *
+	 * @return void
+	 */
+	final public static function assertContainsNotOnlyBool( $haystack, string $message = '' ) {
+		static::assertNotContainsOnly( 'bool', $haystack, true, $message );
+	}
+
+	/**
+	 * Asserts that $haystack only contains values of type callable.
+	 *
+	 * @param iterable<mixed> $haystack The variable to test.
+	 * @param string          $message  Optional failure message to display.
+	 *
+	 * @return void
+	 */
+	final public static function assertContainsOnlyCallable( $haystack, string $message = '' ) {
+		static::assertContainsOnly( 'callable', $haystack, true, $message );
+	}
+
+	/**
+	 * Asserts that $haystack does not only contain values of type callable.
+	 *
+	 * @param iterable<mixed> $haystack The variable to test.
+	 * @param string          $message  Optional failure message to display.
+	 *
+	 * @return void
+	 */
+	final public static function assertContainsNotOnlyCallable( $haystack, string $message = '' ) {
+		static::assertNotContainsOnly( 'callable', $haystack, true, $message );
+	}
+
+	/**
+	 * Asserts that $haystack only contains values of type float.
+	 *
+	 * @param iterable<mixed> $haystack The variable to test.
+	 * @param string          $message  Optional failure message to display.
+	 *
+	 * @return void
+	 */
+	final public static function assertContainsOnlyFloat( $haystack, string $message = '' ) {
+		static::assertContainsOnly( 'float', $haystack, true, $message );
+	}
+
+	/**
+	 * Asserts that $haystack does not only contain values of type float.
+	 *
+	 * @param iterable<mixed> $haystack The variable to test.
+	 * @param string          $message  Optional failure message to display.
+	 *
+	 * @return void
+	 */
+	final public static function assertContainsNotOnlyFloat( $haystack, string $message = '' ) {
+		static::assertNotContainsOnly( 'float', $haystack, true, $message );
+	}
+
+	/**
+	 * Asserts that $haystack only contains values of type integer.
+	 *
+	 * @param iterable<mixed> $haystack The variable to test.
+	 * @param string          $message  Optional failure message to display.
+	 *
+	 * @return void
+	 */
+	final public static function assertContainsOnlyInt( $haystack, string $message = '' ) {
+		static::assertContainsOnly( 'int', $haystack, true, $message );
+	}
+
+	/**
+	 * Asserts that $haystack does not only contain values of type integer.
+	 *
+	 * @param iterable<mixed> $haystack The variable to test.
+	 * @param string          $message  Optional failure message to display.
+	 *
+	 * @return void
+	 */
+	final public static function assertContainsNotOnlyInt( $haystack, string $message = '' ) {
+		static::assertNotContainsOnly( 'int', $haystack, true, $message );
+	}
+
+	/**
+	 * Asserts that $haystack only contains values of type iterable.
+	 *
+	 * {@internal Support for `iterable` was only added to the `IsType` constraint
+	 * in PHPUnit 7.1.0, so this polyfill can't use a direct fall-through to the PHPUnit native
+	 * functionality until the minimum supported PHPUnit version of this library would be PHPUnit 7.1.0.}
+	 *
+	 * @link https://github.com/sebastianbergmann/phpunit/pull/3035 PR which added support for `is_iterable`.
+	 *
+	 * @param iterable<mixed> $haystack The variable to test.
+	 * @param string          $message  Optional failure message to display.
+	 *
+	 * @return void
+	 */
+	final public static function assertContainsOnlyIterable( $haystack, string $message = '' ) {
+		if ( \function_exists( 'is_iterable' ) === true
+			&& \version_compare( Autoload::getPHPUnitVersion(), '7.1.0', '>=' )
+		) {
+			// PHP >= 7.1 with PHPUnit >= 7.1.0.
+			static::assertContainsOnly( 'iterable', $haystack, true, $message );
+		}
+		else {
+			// PHP < 7.1 or PHPUnit 6.x/7.0.0.
+			$exporter = self::getPHPUnitExporterObjectForContainsOnly();
+			$msg      = \sprintf( 'Failed asserting that %s contains only values of type "iterable".', $exporter->export( $haystack ) );
+
+			if ( $message !== '' ) {
+				$msg = $message . \PHP_EOL . $msg;
+			}
+
+			$hasOnlyIterable = true;
+			foreach ( $haystack as $value ) {
+				if ( \is_array( $value ) || $value instanceof Traversable ) {
+					continue;
+				}
+
+				$hasOnlyIterable = false;
+				break;
+			}
+
+			static::assertTrue( $hasOnlyIterable, $msg );
+		}
+	}
+
+	/**
+	 * Asserts that $haystack does not only contain values of type iterable.
+	 *
+	 * {@internal Support for `iterable` was only added to the `IsType` constraint
+	 * in PHPUnit 7.1.0, so this polyfill can't use a direct fall-through to the PHPUnit native
+	 * functionality until the minimum supported PHPUnit version of this library would be PHPUnit 7.1.0.}
+	 *
+	 * @link https://github.com/sebastianbergmann/phpunit/pull/3035 PR which added support for `is_iterable`.
+	 *
+	 * @param iterable<mixed> $haystack The variable to test.
+	 * @param string          $message  Optional failure message to display.
+	 *
+	 * @return void
+	 */
+	final public static function assertContainsNotOnlyIterable( $haystack, string $message = '' ) {
+		if ( \function_exists( 'is_iterable' ) === true
+			&& \version_compare( Autoload::getPHPUnitVersion(), '7.1.0', '>=' )
+		) {
+			// PHP >= 7.1 with PHPUnit >= 7.1.0.
+			static::assertNotContainsOnly( 'iterable', $haystack, true, $message );
+		}
+		else {
+			// PHP < 7.1 or PHPUnit 6.x/7.0.0.
+			$exporter = self::getPHPUnitExporterObjectForContainsOnly();
+			$msg      = \sprintf( 'Failed asserting that %s does not contain only values of type "iterable".', $exporter->export( $haystack ) );
+
+			if ( $message !== '' ) {
+				$msg = $message . \PHP_EOL . $msg;
+			}
+
+			$hasOnlyIterable = true;
+			foreach ( $haystack as $value ) {
+				if ( \is_array( $value ) || $value instanceof Traversable ) {
+					continue;
+				}
+
+				$hasOnlyIterable = false;
+				break;
+			}
+
+			static::assertFalse( $hasOnlyIterable, $msg );
+		}
+	}
+
+	/**
+	 * Asserts that $haystack only contains values of type null.
+	 *
+	 * @param iterable<mixed> $haystack The variable to test.
+	 * @param string          $message  Optional failure message to display.
+	 *
+	 * @return void
+	 */
+	final public static function assertContainsOnlyNull( $haystack, string $message = '' ) {
+		static::assertContainsOnly( 'null', $haystack, true, $message );
+	}
+
+	/**
+	 * Asserts that $haystack does not only contain values of type null.
+	 *
+	 * @param iterable<mixed> $haystack The variable to test.
+	 * @param string          $message  Optional failure message to display.
+	 *
+	 * @return void
+	 */
+	final public static function assertContainsNotOnlyNull( $haystack, string $message = '' ) {
+		static::assertNotContainsOnly( 'null', $haystack, true, $message );
+	}
+
+	/**
+	 * Asserts that $haystack only contains values of type numeric.
+	 *
+	 * @param iterable<mixed> $haystack The variable to test.
+	 * @param string          $message  Optional failure message to display.
+	 *
+	 * @return void
+	 */
+	final public static function assertContainsOnlyNumeric( $haystack, string $message = '' ) {
+		static::assertContainsOnly( 'numeric', $haystack, true, $message );
+	}
+
+	/**
+	 * Asserts that $haystack does not only contain values of type numeric.
+	 *
+	 * @param iterable<mixed> $haystack The variable to test.
+	 * @param string          $message  Optional failure message to display.
+	 *
+	 * @return void
+	 */
+	final public static function assertContainsNotOnlyNumeric( $haystack, string $message = '' ) {
+		static::assertNotContainsOnly( 'numeric', $haystack, true, $message );
+	}
+
+	/**
+	 * Asserts that $haystack only contains values of type object.
+	 *
+	 * @param iterable<mixed> $haystack The variable to test.
+	 * @param string          $message  Optional failure message to display.
+	 *
+	 * @return void
+	 */
+	final public static function assertContainsOnlyObject( $haystack, string $message = '' ) {
+		static::assertContainsOnly( 'object', $haystack, true, $message );
+	}
+
+	/**
+	 * Asserts that $haystack does not only contain values of type object.
+	 *
+	 * @param iterable<mixed> $haystack The variable to test.
+	 * @param string          $message  Optional failure message to display.
+	 *
+	 * @return void
+	 */
+	final public static function assertContainsNotOnlyObject( $haystack, string $message = '' ) {
+		static::assertNotContainsOnly( 'object', $haystack, true, $message );
+	}
+
+	/**
+	 * Asserts that $haystack only contains values of type resource.
+	 *
+	 * @param iterable<mixed> $haystack The variable to test.
+	 * @param string          $message  Optional failure message to display.
+	 *
+	 * @return void
+	 */
+	final public static function assertContainsOnlyResource( $haystack, string $message = '' ) {
+		static::assertContainsOnly( 'resource', $haystack, true, $message );
+	}
+
+	/**
+	 * Asserts that $haystack does not only contain values of type resource.
+	 *
+	 * @param iterable<mixed> $haystack The variable to test.
+	 * @param string          $message  Optional failure message to display.
+	 *
+	 * @return void
+	 */
+	final public static function assertContainsNotOnlyResource( $haystack, string $message = '' ) {
+		static::assertNotContainsOnly( 'resource', $haystack, true, $message );
+	}
+
+	/**
+	 * Asserts that $haystack only contains values of type closed resource.
+	 *
+	 * @param iterable<mixed> $haystack The variable to test.
+	 * @param string          $message  Optional failure message to display.
+	 *
+	 * @return void
+	 */
+	final public static function assertContainsOnlyClosedResource( $haystack, string $message = '' ) {
+		$exporter = self::getPHPUnitExporterObjectForContainsOnly();
+		$msg      = \sprintf( 'Failed asserting that %s contains only values of type "resource (closed)".', $exporter->export( $haystack ) );
+
+		if ( $message !== '' ) {
+			$msg = $message . \PHP_EOL . $msg;
+		}
+
+		$hasOnlyClosedResources = true;
+		foreach ( $haystack as $value ) {
+			if ( ResourceHelper::isClosedResource( $value ) ) {
+				continue;
+			}
+
+			$hasOnlyClosedResources = false;
+			break;
+		}
+
+		static::assertTrue( $hasOnlyClosedResources, $msg );
+	}
+
+	/**
+	 * Asserts that $haystack does not only contain values of type closed resource.
+	 *
+	 * @param iterable<mixed> $haystack The variable to test.
+	 * @param string          $message  Optional failure message to display.
+	 *
+	 * @return void
+	 */
+	final public static function assertContainsNotOnlyClosedResource( $haystack, string $message = '' ) {
+		$exporter = self::getPHPUnitExporterObjectForContainsOnly();
+		$msg      = \sprintf( 'Failed asserting that %s does not contain only values of type "resource (closed)".', $exporter->export( $haystack ) );
+
+		if ( $message !== '' ) {
+			$msg = $message . \PHP_EOL . $msg;
+		}
+
+		$hasOnlyClosedResources = true;
+		foreach ( $haystack as $value ) {
+			if ( ResourceHelper::isClosedResource( $value ) ) {
+				continue;
+			}
+
+			$hasOnlyClosedResources = false;
+			break;
+		}
+
+		static::assertFalse( $hasOnlyClosedResources, $msg );
+	}
+
+	/**
+	 * Asserts that $haystack only contains values of type scalar.
+	 *
+	 * @param iterable<mixed> $haystack The variable to test.
+	 * @param string          $message  Optional failure message to display.
+	 *
+	 * @return void
+	 */
+	final public static function assertContainsOnlyScalar( $haystack, string $message = '' ) {
+		static::assertContainsOnly( 'scalar', $haystack, true, $message );
+	}
+
+	/**
+	 * Asserts that $haystack does not only contain values of type scalar.
+	 *
+	 * @param iterable<mixed> $haystack The variable to test.
+	 * @param string          $message  Optional failure message to display.
+	 *
+	 * @return void
+	 */
+	final public static function assertContainsNotOnlyScalar( $haystack, string $message = '' ) {
+		static::assertNotContainsOnly( 'scalar', $haystack, true, $message );
+	}
+
+	/**
+	 * Asserts that $haystack only contains values of type string.
+	 *
+	 * @param iterable<mixed> $haystack The variable to test.
+	 * @param string          $message  Optional failure message to display.
+	 *
+	 * @return void
+	 */
+	final public static function assertContainsOnlyString( $haystack, string $message = '' ) {
+		static::assertContainsOnly( 'string', $haystack, true, $message );
+	}
+
+	/**
+	 * Asserts that $haystack does not only contain values of type string.
+	 *
+	 * @param iterable<mixed> $haystack The variable to test.
+	 * @param string          $message  Optional failure message to display.
+	 *
+	 * @return void
+	 */
+	final public static function assertContainsNotOnlyString( $haystack, string $message = '' ) {
+		static::assertNotContainsOnly( 'string', $haystack, true, $message );
+	}
+
+	/**
+	 * Helper function to obtain an instance of the Exporter class.
+	 *
+	 * @return Exporter|Exporter_In_Phar|Exporter_In_Phar_Old
+	 */
+	private static function getPHPUnitExporterObjectForContainsOnly() {
+		if ( \class_exists( Exporter::class ) ) {
+			// Composer install or really old PHAR files.
+			return new Exporter();
+		}
+		elseif ( \class_exists( Exporter_In_Phar::class ) ) {
+			// PHPUnit PHAR file for 8.5.38+, 9.6.19+, 10.5.17+ and 11.0.10+.
+			return new Exporter_In_Phar();
+		}
+
+		// PHPUnit PHAR file for < 8.5.38, < 9.6.19, < 10.5.17 and < 11.0.10.
+		return new Exporter_In_Phar_Old();
+	}
+}
diff --git a/src/Polyfills/AssertContainsOnly_Empty.php b/src/Polyfills/AssertContainsOnly_Empty.php
new file mode 100644
index 0000000..e2b29c9
--- /dev/null
+++ b/src/Polyfills/AssertContainsOnly_Empty.php
@@ -0,0 +1,10 @@
+<?php
+
+namespace Yoast\PHPUnitPolyfills\Polyfills;
+
+/**
+ * Empty trait for use with PHPUnit >= 11.5.0 in which the polyfill is not needed.
+ *
+ * @since 3.1.0
+ */
+trait AssertContainsOnly {}
diff --git a/src/TestCases/TestCasePHPUnitGte8.php b/src/TestCases/TestCasePHPUnitGte8.php
index 3b9bde0..930a6a2 100644
--- a/src/TestCases/TestCasePHPUnitGte8.php
+++ b/src/TestCases/TestCasePHPUnitGte8.php
@@ -5,6 +5,7 @@
 use PHPUnit\Framework\TestCase as PHPUnit_TestCase;
 use Yoast\PHPUnitPolyfills\Polyfills\AssertArrayWithListKeys;
 use Yoast\PHPUnitPolyfills\Polyfills\AssertClosedResource;
+use Yoast\PHPUnitPolyfills\Polyfills\AssertContainsOnly;
 use Yoast\PHPUnitPolyfills\Polyfills\AssertFileEqualsSpecializations;
 use Yoast\PHPUnitPolyfills\Polyfills\AssertIgnoringLineEndings;
 use Yoast\PHPUnitPolyfills\Polyfills\AssertionRenames;
@@ -31,6 +32,7 @@ abstract class TestCase extends PHPUnit_TestCase {
 
 	use AssertArrayWithListKeys;
 	use AssertClosedResource;
+	use AssertContainsOnly;
 	use AssertFileEqualsSpecializations;
 	use AssertIgnoringLineEndings;
 	use AssertionRenames;
diff --git a/src/TestCases/TestCasePHPUnitLte7.php b/src/TestCases/TestCasePHPUnitLte7.php
index 078a748..e9becbd 100644
--- a/src/TestCases/TestCasePHPUnitLte7.php
+++ b/src/TestCases/TestCasePHPUnitLte7.php
@@ -5,6 +5,7 @@
 use PHPUnit\Framework\TestCase as PHPUnit_TestCase;
 use Yoast\PHPUnitPolyfills\Polyfills\AssertArrayWithListKeys;
 use Yoast\PHPUnitPolyfills\Polyfills\AssertClosedResource;
+use Yoast\PHPUnitPolyfills\Polyfills\AssertContainsOnly;
 use Yoast\PHPUnitPolyfills\Polyfills\AssertEqualsSpecializations;
 use Yoast\PHPUnitPolyfills\Polyfills\AssertFileEqualsSpecializations;
 use Yoast\PHPUnitPolyfills\Polyfills\AssertIgnoringLineEndings;
@@ -34,6 +35,7 @@ abstract class TestCase extends PHPUnit_TestCase {
 
 	use AssertArrayWithListKeys;
 	use AssertClosedResource;
+	use AssertContainsOnly;
 	use AssertEqualsSpecializations;
 	use AssertFileEqualsSpecializations;
 	use AssertIgnoringLineEndings;
diff --git a/src/TestCases/XTestCase.php b/src/TestCases/XTestCase.php
index f9762e7..ccba952 100644
--- a/src/TestCases/XTestCase.php
+++ b/src/TestCases/XTestCase.php
@@ -9,6 +9,7 @@
 use PHPUnit\Framework\TestCase as PHPUnit_TestCase;
 use Yoast\PHPUnitPolyfills\Polyfills\AssertArrayWithListKeys;
 use Yoast\PHPUnitPolyfills\Polyfills\AssertClosedResource;
+use Yoast\PHPUnitPolyfills\Polyfills\AssertContainsOnly;
 use Yoast\PHPUnitPolyfills\Polyfills\AssertEqualsSpecializations;
 use Yoast\PHPUnitPolyfills\Polyfills\AssertFileEqualsSpecializations;
 use Yoast\PHPUnitPolyfills\Polyfills\AssertIgnoringLineEndings;
@@ -40,6 +41,7 @@ abstract class XTestCase extends PHPUnit_TestCase {
 
 	use AssertArrayWithListKeys;
 	use AssertClosedResource;
+	use AssertContainsOnly;
 	use AssertEqualsSpecializations;
 	use AssertFileEqualsSpecializations;
 	use AssertIgnoringLineEndings;
diff --git a/tests/Polyfills/AssertContainsOnlyTest.php b/tests/Polyfills/AssertContainsOnlyTest.php
new file mode 100644
index 0000000..e2bacbc
--- /dev/null
+++ b/tests/Polyfills/AssertContainsOnlyTest.php
@@ -0,0 +1,1243 @@
+<?php
+
+namespace Yoast\PHPUnitPolyfills\Tests\Polyfills;
+
+use DateTime;
+use EmptyIterator;
+use PHPUnit\Framework\AssertionFailedError;
+use PHPUnit\Framework\Attributes\AfterClass;
+use PHPUnit\Framework\Attributes\BeforeClass;
+use PHPUnit\Framework\Attributes\CoversClass;
+use PHPUnit\Framework\Attributes\DataProvider;
+use PHPUnit\Framework\TestCase;
+use stdClass;
+use Yoast\PHPUnitPolyfills\Polyfills\AssertContainsOnly;
+use Yoast\PHPUnitPolyfills\Polyfills\ExpectExceptionMessageMatches;
+
+/**
+ * Tests for the functions polyfilled by the AssertContainsOnly trait.
+ *
+ * @covers \Yoast\PHPUnitPolyfills\Polyfills\AssertContainsOnly
+ */
+#[CoversClass( AssertContainsOnly::class )]
+final class AssertContainsOnlyTest extends TestCase {
+
+	use AssertContainsOnly;
+	use ExpectExceptionMessageMatches;
+
+	/**
+	 * Resource for use in the tests.
+	 *
+	 * @var resource
+	 */
+	private static $openResource;
+
+	/**
+	 * Closed resource for use in the tests.
+	 *
+	 * @var resource
+	 */
+	private static $closedResource;
+
+	/**
+	 * Create some resources for use in the tests.
+	 *
+	 * @beforeClass
+	 *
+	 * @return void
+	 */
+	#[BeforeClass]
+	public static function prepareResource() {
+		self::$openResource = \opendir( __DIR__ );
+
+		self::$closedResource = \opendir( __DIR__ );
+		\closedir( self::$closedResource );
+	}
+
+	/**
+	 * Clean up the previously created and still open resource.
+	 *
+	 * @afterClass
+	 *
+	 * @return void
+	 */
+	#[AfterClass]
+	public static function closeResource() {
+		\closedir( self::$openResource );
+	}
+
+	/**
+	 * Data provider.
+	 *
+	 * @return array<mixed>
+	 */
+	public static function dataMixedValues() {
+		return [
+			'Array with a mix of values' => [
+				[
+					null,
+					false,
+					10,
+					4.34,
+					'string',
+					'is_callable',
+					[ 'not', 'empty' ],
+					new stdClass(),
+				],
+			],
+		];
+	}
+
+	/**
+	 * Verify the assertContainsNotOnlyInstancesOf() method succeeds for valid input.
+	 *
+	 * @dataProvider dataMixedValues
+	 *
+	 * @param array<mixed> $haystack Haystack.
+	 *
+	 * @return void
+	 */
+	#[DataProvider( 'dataMixedValues' )]
+	public function testAssertContainsNotOnlyInstancesOfSucceeds( $haystack ) {
+		$this->assertContainsNotOnlyInstancesOf( stdClass::class, $haystack );
+	}
+
+	/**
+	 * Verify the assertContainsNotOnlyInstancesOf() method fails on invalid input.
+	 *
+	 * @return void
+	 */
+	public function testAssertContainsNotOnlyInstancesOfFails() {
+		$pattern = '`(\]|\)) does not contain only values of type "stdClass"\.$`';
+
+		$this->expectException( AssertionFailedError::class );
+		$this->expectExceptionMessageMatches( $pattern );
+
+		$haystack = [
+			new stdClass( 1, 2, 3 ),
+			new stdClass(),
+			new stdClass( 'foo' ),
+		];
+
+		self::assertContainsNotOnlyInstancesOf( stdClass::class, $haystack );
+	}
+
+	/**
+	 * Verify the assertContainsOnlyArray() method succeeds for valid input.
+	 *
+	 * @dataProvider dataOnlyArray
+	 *
+	 * @param array<int|string> $haystack Haystack.
+	 *
+	 * @return void
+	 */
+	#[DataProvider( 'dataOnlyArray' )]
+	public function testAssertContainsOnlyArraySucceeds( $haystack ) {
+		self::assertContainsOnlyArray( $haystack );
+	}
+
+	/**
+	 * Verify the assertContainsOnlyArray() method fails on invalid input.
+	 *
+	 * @dataProvider dataMixedValues
+	 *
+	 * @param array<mixed> $haystack Haystack.
+	 *
+	 * @return void
+	 */
+	#[DataProvider( 'dataMixedValues' )]
+	public function testAssertContainsOnlyArrayFails( $haystack ) {
+		$pattern = '`(\]|\)) contains only values of type "array"\.$`';
+
+		$this->expectException( AssertionFailedError::class );
+		$this->expectExceptionMessageMatches( $pattern );
+
+		static::assertContainsOnlyArray( $haystack );
+	}
+
+	/**
+	 * Verify the assertContainsNotOnlyArray() method succeeds for valid input.
+	 *
+	 * @dataProvider dataMixedValues
+	 *
+	 * @param array<mixed> $haystack Haystack.
+	 *
+	 * @return void
+	 */
+	#[DataProvider( 'dataMixedValues' )]
+	public function testAssertContainsNotOnlyArraySucceeds( $haystack ) {
+		static::assertContainsNotOnlyArray( $haystack );
+	}
+
+	/**
+	 * Verify the assertContainsNotOnlyArray() method fails on invalid input.
+	 *
+	 * @dataProvider dataOnlyArray
+	 *
+	 * @param array<int|string> $haystack Haystack.
+	 *
+	 * @return void
+	 */
+	#[DataProvider( 'dataOnlyArray' )]
+	public function testAssertContainsNotOnlyArrayFails( $haystack ) {
+		$pattern = '`(\]|\)) does not contain only values of type "array"\.$`';
+
+		$this->expectException( AssertionFailedError::class );
+		$this->expectExceptionMessageMatches( $pattern );
+
+		self::assertContainsNotOnlyArray( $haystack );
+	}
+
+	/**
+	 * Data provider.
+	 *
+	 * @return array<array<int|string>>
+	 */
+	public static function dataOnlyArray() {
+		return [
+			'Array containing only arrays' => [
+				[
+					[],
+					[ 1, 2, 3 ],
+					[ 'foo' => 'bar' ],
+				],
+			],
+		];
+	}
+
+	/**
+	 * Verify the assertContainsOnlyBool() method succeeds for valid input.
+	 *
+	 * @dataProvider dataOnlyBool
+	 *
+	 * @param array<bool> $haystack Haystack.
+	 *
+	 * @return void
+	 */
+	#[DataProvider( 'dataOnlyBool' )]
+	public function testAssertContainsOnlyBoolSucceeds( $haystack ) {
+		$this->assertContainsOnlyBool( $haystack );
+	}
+
+	/**
+	 * Verify the assertContainsOnlyBool() method fails on invalid input.
+	 *
+	 * @dataProvider dataMixedValues
+	 *
+	 * @param array<mixed> $haystack Haystack.
+	 *
+	 * @return void
+	 */
+	#[DataProvider( 'dataMixedValues' )]
+	public function testAssertContainsOnlyBoolFails( $haystack ) {
+		$pattern = '`(\]|\)) contains only values of type "bool"\.$`';
+
+		$this->expectException( AssertionFailedError::class );
+		$this->expectExceptionMessageMatches( $pattern );
+
+		self::assertContainsOnlyBool( $haystack );
+	}
+
+	/**
+	 * Verify the assertContainsNotOnlyBool() method succeeds for valid input.
+	 *
+	 * @dataProvider dataMixedValues
+	 *
+	 * @param array<mixed> $haystack Haystack.
+	 *
+	 * @return void
+	 */
+	#[DataProvider( 'dataMixedValues' )]
+	public function testAssertContainsNotOnlyBoolSucceeds( $haystack ) {
+		$this->assertContainsNotOnlyBool( $haystack );
+	}
+
+	/**
+	 * Verify the assertContainsNotOnlyBool() method fails on invalid input.
+	 *
+	 * @dataProvider dataOnlyBool
+	 *
+	 * @param array<bool> $haystack Haystack.
+	 *
+	 * @return void
+	 */
+	#[DataProvider( 'dataOnlyBool' )]
+	public function testAssertContainsNotOnlyBoolFails( $haystack ) {
+		$pattern = '`(\]|\)) does not contain only values of type "bool"\.$`';
+
+		$this->expectException( AssertionFailedError::class );
+		$this->expectExceptionMessageMatches( $pattern );
+
+		static::assertContainsNotOnlyBool( $haystack );
+	}
+
+	/**
+	 * Data provider.
+	 *
+	 * @return array<array<bool>>
+	 */
+	public static function dataOnlyBool() {
+		return [
+			'Array containing only booleans' => [
+				[
+					0     => false,
+					1     => true,
+					'foo' => true,
+				],
+			],
+		];
+	}
+
+	/**
+	 * Verify the assertContainsOnlyCallable() method succeeds for valid input.
+	 *
+	 * @dataProvider dataOnlyCallable
+	 *
+	 * @param array<callable> $haystack Haystack.
+	 *
+	 * @return void
+	 */
+	#[DataProvider( 'dataOnlyCallable' )]
+	public function testAssertContainsOnlyCallableSucceeds( $haystack ) {
+		$this->assertContainsOnlyCallable( $haystack );
+	}
+
+	/**
+	 * Verify the assertContainsOnlyCallable() method fails on invalid input.
+	 *
+	 * @dataProvider dataMixedValues
+	 *
+	 * @param array<mixed> $haystack Haystack.
+	 *
+	 * @return void
+	 */
+	#[DataProvider( 'dataMixedValues' )]
+	public function testAssertContainsOnlyCallableFails( $haystack ) {
+		$pattern = '`(\]|\)) contains only values of type "callable"\.$`';
+
+		$this->expectException( AssertionFailedError::class );
+		$this->expectExceptionMessageMatches( $pattern );
+
+		self::assertContainsOnlyCallable( $haystack );
+	}
+
+	/**
+	 * Verify the assertContainsNotOnlyCallable() method succeeds for valid input.
+	 *
+	 * @dataProvider dataMixedValues
+	 *
+	 * @param array<mixed> $haystack Haystack.
+	 *
+	 * @return void
+	 */
+	#[DataProvider( 'dataMixedValues' )]
+	public function testAssertContainsNotOnlyCallableSucceeds( $haystack ) {
+		$this->assertContainsNotOnlyCallable( $haystack );
+	}
+
+	/**
+	 * Verify the assertContainsNotOnlyCallable() method fails on invalid input.
+	 *
+	 * @dataProvider dataOnlyCallable
+	 *
+	 * @param array<callable> $haystack Haystack.
+	 *
+	 * @return void
+	 */
+	#[DataProvider( 'dataOnlyCallable' )]
+	public function testAssertContainsNotOnlyCallableFails( $haystack ) {
+		$pattern = '`(\]|\)) does not contain only values of type "callable"\.$`';
+
+		$this->expectException( AssertionFailedError::class );
+		$this->expectExceptionMessageMatches( $pattern );
+
+		self::assertContainsNotOnlyCallable( $haystack );
+	}
+
+	/**
+	 * Data provider.
+	 *
+	 * @return array<array<callable>>
+	 */
+	public static function dataOnlyCallable() {
+		return [
+			'Array containing only callables' => [
+				[
+					'is_string',
+					[ self::class, 'dummyCallable' ],
+				],
+			],
+		];
+	}
+
+	/**
+	 * Dummy method to have a callable method available.
+	 *
+	 * @return void
+	 */
+	public static function dummyCallable() {
+		// Nothing to see here.
+	}
+
+	/**
+	 * Verify the assertContainsOnlyFloat() method succeeds for valid input.
+	 *
+	 * @dataProvider dataOnlyFloat
+	 *
+	 * @param array<float> $haystack Haystack.
+	 *
+	 * @return void
+	 */
+	#[DataProvider( 'dataOnlyFloat' )]
+	public function testAssertContainsOnlyFloatSucceeds( $haystack ) {
+		self::assertContainsOnlyFloat( $haystack );
+	}
+
+	/**
+	 * Verify the assertContainsOnlyFloat() method fails on invalid input.
+	 *
+	 * @dataProvider dataMixedValues
+	 *
+	 * @param array<mixed> $haystack Haystack.
+	 *
+	 * @return void
+	 */
+	#[DataProvider( 'dataMixedValues' )]
+	public function testAssertContainsOnlyFloatFails( $haystack ) {
+		$pattern = '`(\]|\)) contains only values of type "float"\.$`';
+
+		$this->expectException( AssertionFailedError::class );
+		$this->expectExceptionMessageMatches( $pattern );
+
+		self::assertContainsOnlyFloat( $haystack );
+	}
+
+	/**
+	 * Verify the assertContainsNotOnlyFloat() method succeeds for valid input.
+	 *
+	 * @dataProvider dataMixedValues
+	 *
+	 * @param array<mixed> $haystack Haystack.
+	 *
+	 * @return void
+	 */
+	#[DataProvider( 'dataMixedValues' )]
+	public function testAssertContainsNotOnlyFloatSucceeds( $haystack ) {
+		$this->assertContainsNotOnlyFloat( $haystack );
+	}
+
+	/**
+	 * Verify the assertContainsNotOnlyFloat() method fails on invalid input.
+	 *
+	 * @dataProvider dataOnlyFloat
+	 *
+	 * @param array<float> $haystack Haystack.
+	 *
+	 * @return void
+	 */
+	#[DataProvider( 'dataOnlyFloat' )]
+	public function testAssertContainsNotOnlyFloatFails( $haystack ) {
+		$pattern = '`(\]|\)) does not contain only values of type "float"\.$`';
+
+		$this->expectException( AssertionFailedError::class );
+		$this->expectExceptionMessageMatches( $pattern );
+
+		self::assertContainsNotOnlyFloat( $haystack );
+	}
+
+	/**
+	 * Data provider.
+	 *
+	 * @return array<array<float>>
+	 */
+	public static function dataOnlyFloat() {
+		return [
+			'Array containing only floats' => [
+				[
+					0.0,
+					3.5,
+					-0.5645,
+					8E7,
+				],
+			],
+		];
+	}
+
+	/**
+	 * Verify the assertContainsOnlyInt() method succeeds for valid input.
+	 *
+	 * @dataProvider dataOnlyInt
+	 *
+	 * @param array<int> $haystack Haystack.
+	 *
+	 * @return void
+	 */
+	#[DataProvider( 'dataOnlyInt' )]
+	public function testAssertContainsOnlyIntSucceeds( $haystack ) {
+		$this->assertContainsOnlyInt( $haystack );
+	}
+
+	/**
+	 * Verify the assertContainsOnlyInt() method fails on invalid input.
+	 *
+	 * @dataProvider dataMixedValues
+	 *
+	 * @param array<mixed> $haystack Haystack.
+	 *
+	 * @return void
+	 */
+	#[DataProvider( 'dataMixedValues' )]
+	public function testAssertContainsOnlyIntFails( $haystack ) {
+		$pattern = '`(\]|\)) contains only values of type "int"\.$`';
+
+		$this->expectException( AssertionFailedError::class );
+		$this->expectExceptionMessageMatches( $pattern );
+
+		self::assertContainsOnlyInt( $haystack );
+	}
+
+	/**
+	 * Verify the assertContainsNotOnlyInt() method succeeds for valid input.
+	 *
+	 * @dataProvider dataMixedValues
+	 *
+	 * @param array<mixed> $haystack Haystack.
+	 *
+	 * @return void
+	 */
+	#[DataProvider( 'dataMixedValues' )]
+	public function testAssertContainsNotOnlyIntSucceeds( $haystack ) {
+		$this->assertContainsNotOnlyInt( $haystack );
+	}
+
+	/**
+	 * Verify the assertContainsNotOnlyInt() method fails on invalid input.
+	 *
+	 * @dataProvider dataOnlyInt
+	 *
+	 * @param array<int> $haystack Haystack.
+	 *
+	 * @return void
+	 */
+	#[DataProvider( 'dataOnlyInt' )]
+	public function testAssertContainsNotOnlyIntFails( $haystack ) {
+		$pattern = '`(\]|\)) does not contain only values of type "int"\.$`';
+
+		$this->expectException( AssertionFailedError::class );
+		$this->expectExceptionMessageMatches( $pattern );
+
+		$this->assertContainsNotOnlyInt( $haystack );
+	}
+
+	/**
+	 * Data provider.
+	 *
+	 * @return array<array<int>>
+	 */
+	public static function dataOnlyInt() {
+		return [
+			'Array containing only integers' => [
+				[
+					0,
+					10,
+					-2,
+					0b010100,
+					0x7AB,
+					\PHP_INT_MAX,
+				],
+			],
+		];
+	}
+
+	/**
+	 * Verify the assertContainsOnlyIterable() method succeeds for valid input.
+	 *
+	 * @dataProvider dataOnlyIterable
+	 *
+	 * @param array<iterable<mixed>> $haystack Haystack.
+	 *
+	 * @return void
+	 */
+	#[DataProvider( 'dataOnlyIterable' )]
+	public function testAssertContainsOnlyIterableSucceeds( $haystack ) {
+		$this->assertContainsOnlyIterable( $haystack );
+	}
+
+	/**
+	 * Verify the assertContainsOnlyIterable() method fails on invalid input.
+	 *
+	 * @dataProvider dataMixedValues
+	 *
+	 * @param array<mixed> $haystack Haystack.
+	 *
+	 * @return void
+	 */
+	#[DataProvider( 'dataMixedValues' )]
+	public function testAssertContainsOnlyIterableFails( $haystack ) {
+		$pattern = '`(\]|\)) contains only values of type "iterable"\.`';
+
+		$this->expectException( AssertionFailedError::class );
+		$this->expectExceptionMessageMatches( $pattern );
+
+		$this->assertContainsOnlyIterable( $haystack );
+	}
+
+	/**
+	 * Verify that the assertContainsOnlyIterable() method fails a test with the correct custom failure message,
+	 * when the custom $message parameter has been passed.
+	 *
+	 * @dataProvider dataMixedValues
+	 *
+	 * @param array<mixed> $haystack Haystack.
+	 *
+	 * @return void
+	 */
+	#[DataProvider( 'dataMixedValues' )]
+	public function testAssertContainsOnlyIterableFailsWithCustomMessage( $haystack ) {
+		$pattern = '`^This assertion failed for reason XYZ\R`';
+
+		$this->expectException( AssertionFailedError::class );
+		$this->expectExceptionMessageMatches( $pattern );
+
+		$this->assertContainsOnlyIterable( $haystack, 'This assertion failed for reason XYZ' );
+	}
+
+	/**
+	 * Verify the assertContainsNotOnlyIterable() method succeeds for valid input.
+	 *
+	 * @dataProvider dataMixedValues
+	 *
+	 * @param array<mixed> $haystack Haystack.
+	 *
+	 * @return void
+	 */
+	#[DataProvider( 'dataMixedValues' )]
+	public function testAssertContainsNotOnlyIterableSucceeds( $haystack ) {
+		$this->assertContainsNotOnlyIterable( $haystack );
+	}
+
+	/**
+	 * Verify the assertContainsNotOnlyIterable() method fails on invalid input.
+	 *
+	 * @dataProvider dataOnlyIterable
+	 *
+	 * @param array<iterable<mixed>> $haystack Haystack.
+	 *
+	 * @return void
+	 */
+	#[DataProvider( 'dataOnlyIterable' )]
+	public function testAssertContainsNotOnlyIterableFails( $haystack ) {
+		$pattern = '`(\]|\)) does not contain only values of type "iterable"\.`';
+
+		$this->expectException( AssertionFailedError::class );
+		$this->expectExceptionMessageMatches( $pattern );
+
+		static::assertContainsNotOnlyIterable( $haystack );
+	}
+
+	/**
+	 * Verify that the assertContainsNotOnlyIterable() method fails a test with the correct custom failure message,
+	 * when the custom $message parameter has been passed.
+	 *
+	 * @dataProvider dataOnlyIterable
+	 *
+	 * @param array<iterable<mixed>> $haystack Haystack.
+	 *
+	 * @return void
+	 */
+	#[DataProvider( 'dataOnlyIterable' )]
+	public function testAssertContainsNotOnlyIterableFailsWithCustomMessage( $haystack ) {
+		$pattern = '`^This assertion failed for reason XYZ\R`';
+
+		$this->expectException( AssertionFailedError::class );
+		$this->expectExceptionMessageMatches( $pattern );
+
+		self::assertContainsNotOnlyIterable( $haystack, 'This assertion failed for reason XYZ' );
+	}
+
+	/**
+	 * Data provider.
+	 *
+	 * @return array<array<iterable<mixed>>>
+	 */
+	public static function dataOnlyIterable() {
+		return [
+			'Array containing only iterables' => [
+				[
+					[],
+					[ 1, 2, 3 ],
+					[ 'foo' => 'bar' ],
+					new EmptyIterator(),
+				],
+			],
+		];
+	}
+
+	/**
+	 * Verify the assertContainsOnlyNull() method succeeds for valid input.
+	 *
+	 * @dataProvider dataOnlyNull
+	 *
+	 * @param array<null> $haystack Haystack.
+	 *
+	 * @return void
+	 */
+	#[DataProvider( 'dataOnlyNull' )]
+	public function testAssertContainsOnlyNullSucceeds( $haystack ) {
+		self::assertContainsOnlyNull( $haystack );
+	}
+
+	/**
+	 * Verify the assertContainsOnlyNull() method fails on invalid input.
+	 *
+	 * @dataProvider dataMixedValues
+	 *
+	 * @param array<mixed> $haystack Haystack.
+	 *
+	 * @return void
+	 */
+	#[DataProvider( 'dataMixedValues' )]
+	public function testAssertContainsOnlyNullFails( $haystack ) {
+		$pattern = '`(\]|\)) contains only values of type "null"\.$`';
+
+		$this->expectException( AssertionFailedError::class );
+		$this->expectExceptionMessageMatches( $pattern );
+
+		self::assertContainsOnlyNull( $haystack );
+	}
+
+	/**
+	 * Verify the assertContainsNotOnlyNull() method succeeds for valid input.
+	 *
+	 * @dataProvider dataMixedValues
+	 *
+	 * @param array<mixed> $haystack Haystack.
+	 *
+	 * @return void
+	 */
+	#[DataProvider( 'dataMixedValues' )]
+	public function testAssertContainsNotOnlyNullSucceeds( $haystack ) {
+		$this->assertContainsNotOnlyNull( $haystack );
+	}
+
+	/**
+	 * Verify the assertContainsNotOnlyNull() method fails on invalid input.
+	 *
+	 * @dataProvider dataOnlyNull
+	 *
+	 * @param array<null> $haystack Haystack.
+	 *
+	 * @return void
+	 */
+	#[DataProvider( 'dataOnlyNull' )]
+	public function testAssertContainsNotOnlyNullFails( $haystack ) {
+		$pattern = '`(\]|\)) does not contain only values of type "null"\.$`';
+
+		$this->expectException( AssertionFailedError::class );
+		$this->expectExceptionMessageMatches( $pattern );
+
+		self::assertContainsNotOnlyNull( $haystack );
+	}
+
+	/**
+	 * Data provider.
+	 *
+	 * @return array<array<null>>
+	 */
+	public static function dataOnlyNull() {
+		return [
+			'Array containing only nulls' => [
+				[
+					null,
+					null,
+				],
+			],
+		];
+	}
+
+	/**
+	 * Verify the assertContainsOnlyNumeric() method succeeds for valid input.
+	 *
+	 * @dataProvider dataOnlyNumeric
+	 *
+	 * @param array<int|float|string> $haystack Haystack.
+	 *
+	 * @return void
+	 */
+	#[DataProvider( 'dataOnlyNumeric' )]
+	public function testAssertContainsOnlyNumericSucceeds( $haystack ) {
+		self::assertContainsOnlyNumeric( $haystack );
+	}
+
+	/**
+	 * Verify the assertContainsOnlyNumeric() method fails on invalid input.
+	 *
+	 * @dataProvider dataMixedValues
+	 *
+	 * @param array<mixed> $haystack Haystack.
+	 *
+	 * @return void
+	 */
+	#[DataProvider( 'dataMixedValues' )]
+	public function testAssertContainsOnlyNumericFails( $haystack ) {
+		$pattern = '`(\]|\)) contains only values of type "numeric"\.$`';
+
+		$this->expectException( AssertionFailedError::class );
+		$this->expectExceptionMessageMatches( $pattern );
+
+		static::assertContainsOnlyNumeric( $haystack );
+	}
+
+	/**
+	 * Verify the assertContainsNotOnlyNumeric() method succeeds for valid input.
+	 *
+	 * @dataProvider dataMixedValues
+	 *
+	 * @param array<mixed> $haystack Haystack.
+	 *
+	 * @return void
+	 */
+	#[DataProvider( 'dataMixedValues' )]
+	public function testAssertContainsNotOnlyNumericSucceeds( $haystack ) {
+		$this->assertContainsNotOnlyNumeric( $haystack );
+	}
+
+	/**
+	 * Verify the assertContainsNotOnlyNumeric() method fails on invalid input.
+	 *
+	 * @dataProvider dataOnlyNumeric
+	 *
+	 * @param array<int|float|string> $haystack Haystack.
+	 *
+	 * @return void
+	 */
+	#[DataProvider( 'dataOnlyNumeric' )]
+	public function testAssertContainsNotOnlyNumericFails( $haystack ) {
+		$pattern = '`(\]|\)) does not contain only values of type "numeric"\.$`';
+
+		$this->expectException( AssertionFailedError::class );
+		$this->expectExceptionMessageMatches( $pattern );
+
+		self::assertContainsNotOnlyNumeric( $haystack );
+	}
+
+	/**
+	 * Data provider.
+	 *
+	 * @return array<array<int|float|string>>
+	 */
+	public static function dataOnlyNumeric() {
+		return [
+			'Array containing only numerics' => [
+				[
+					'0',
+					'12344',
+					0,
+					1235,
+					0.0,
+					-12.4,
+				],
+			],
+		];
+	}
+
+	/**
+	 * Verify the assertContainsOnlyObject() method succeeds for valid input.
+	 *
+	 * @dataProvider dataOnlyObject
+	 *
+	 * @param array<object> $haystack Haystack.
+	 *
+	 * @return void
+	 */
+	#[DataProvider( 'dataOnlyObject' )]
+	public function testAssertContainsOnlyObjectSucceeds( $haystack ) {
+		$this->assertContainsOnlyObject( $haystack );
+	}
+
+	/**
+	 * Verify the assertContainsOnlyObject() method fails on invalid input.
+	 *
+	 * @dataProvider dataMixedValues
+	 *
+	 * @param array<mixed> $haystack Haystack.
+	 *
+	 * @return void
+	 */
+	#[DataProvider( 'dataMixedValues' )]
+	public function testAssertContainsOnlyObjectFails( $haystack ) {
+		$pattern = '`(\]|\)) contains only values of type "object"\.$`';
+
+		$this->expectException( AssertionFailedError::class );
+		$this->expectExceptionMessageMatches( $pattern );
+
+		self::assertContainsOnlyObject( $haystack );
+	}
+
+	/**
+	 * Verify the assertContainsNotOnlyObject() method succeeds for valid input.
+	 *
+	 * @dataProvider dataMixedValues
+	 *
+	 * @param array<mixed> $haystack Haystack.
+	 *
+	 * @return void
+	 */
+	#[DataProvider( 'dataMixedValues' )]
+	public function testAssertContainsNotOnlyObjectSucceeds( $haystack ) {
+		$this->assertContainsNotOnlyObject( $haystack );
+	}
+
+	/**
+	 * Verify the assertContainsNotOnlyObject() method fails on invalid input.
+	 *
+	 * @dataProvider dataOnlyObject
+	 *
+	 * @param array<object> $haystack Haystack.
+	 *
+	 * @return void
+	 */
+	#[DataProvider( 'dataOnlyObject' )]
+	public function testAssertContainsNotOnlyObjectFails( $haystack ) {
+		$pattern = '`(\]|\)) does not contain only values of type "object"\.$`';
+
+		$this->expectException( AssertionFailedError::class );
+		$this->expectExceptionMessageMatches( $pattern );
+
+		self::assertContainsNotOnlyObject( $haystack );
+	}
+
+	/**
+	 * Data provider.
+	 *
+	 * @return array<array<object>>
+	 */
+	public static function dataOnlyObject() {
+		return [
+			'Array containing only objects' => [
+				[
+					new stdClass(),
+					new EmptyIterator(),
+					new DateTime(),
+				],
+			],
+		];
+	}
+
+	/**
+	 * Verify the assertContainsOnlyResource() method succeeds for valid input.
+	 *
+	 * @return void
+	 */
+	public function testAssertContainsOnlyResourceSucceeds() {
+		$this->assertContainsOnlyResource( [ self::$openResource, self::$closedResource ] );
+	}
+
+	/**
+	 * Verify the assertContainsOnlyResource() method fails on invalid input.
+	 *
+	 * @dataProvider dataMixedValues
+	 *
+	 * @param array<mixed> $haystack Haystack.
+	 *
+	 * @return void
+	 */
+	#[DataProvider( 'dataMixedValues' )]
+	public function testAssertContainsOnlyResourceFails( $haystack ) {
+		$pattern = '`(\]|\)) contains only values of type "resource"\.$`';
+
+		$this->expectException( AssertionFailedError::class );
+		$this->expectExceptionMessageMatches( $pattern );
+
+		self::assertContainsOnlyResource( $haystack );
+	}
+
+	/**
+	 * Verify the assertContainsNotOnlyResource() method succeeds for valid input.
+	 *
+	 * @dataProvider dataMixedValues
+	 *
+	 * @param array<mixed> $haystack Haystack.
+	 *
+	 * @return void
+	 */
+	#[DataProvider( 'dataMixedValues' )]
+	public function testAssertContainsNotOnlyResourceSucceeds( $haystack ) {
+		static::assertContainsNotOnlyResource( $haystack );
+	}
+
+	/**
+	 * Verify the assertContainsNotOnlyResource() method fails on invalid input.
+	 *
+	 * @return void
+	 */
+	public function testAssertContainsNotOnlyResourceFails() {
+		$pattern = '`(\]|\)) does not contain only values of type "resource"\.$`';
+
+		$this->expectException( AssertionFailedError::class );
+		$this->expectExceptionMessageMatches( $pattern );
+
+		$this->assertContainsNotOnlyResource( [ self::$openResource, self::$closedResource ] );
+	}
+
+	/**
+	 * Verify the assertContainsOnlyClosedResource() method succeeds for valid input.
+	 *
+	 * @return void
+	 */
+	public function testAssertContainsOnlyClosedResourceSucceeds() {
+		$this->assertContainsOnlyClosedResource( [ self::$closedResource ] );
+	}
+
+	/**
+	 * Verify the assertContainsOnlyClosedResource() method fails on invalid input.
+	 *
+	 * @dataProvider dataMixedValues
+	 *
+	 * @param array<mixed> $haystack Haystack.
+	 *
+	 * @return void
+	 */
+	#[DataProvider( 'dataMixedValues' )]
+	public function testAssertContainsOnlyClosedResourceFails( $haystack ) {
+		$pattern = '`(\]|\)) contains only values of type "resource \(closed\)"\.`';
+
+		$this->expectException( AssertionFailedError::class );
+		$this->expectExceptionMessageMatches( $pattern );
+
+		self::assertContainsOnlyClosedResource( $haystack );
+	}
+
+	/**
+	 * Verify that the assertContainsOnlyClosedResource() method fails a test with the correct custom failure message,
+	 * when the custom $message parameter has been passed.
+	 *
+	 * @dataProvider dataMixedValues
+	 *
+	 * @param array<mixed> $haystack Haystack.
+	 *
+	 * @return void
+	 */
+	#[DataProvider( 'dataMixedValues' )]
+	public function testAssertContainsOnlyClosedResourceFailsWithCustomMessage( $haystack ) {
+		$pattern = '`^This assertion failed for reason XYZ\R`';
+
+		$this->expectException( AssertionFailedError::class );
+		$this->expectExceptionMessageMatches( $pattern );
+
+		$this->assertContainsOnlyClosedResource( $haystack, 'This assertion failed for reason XYZ' );
+	}
+
+	/**
+	 * Verify the assertContainsNotOnlyClosedResource() method succeeds for valid input.
+	 *
+	 * @dataProvider dataMixedValues
+	 *
+	 * @param array<mixed> $haystack Haystack.
+	 *
+	 * @return void
+	 */
+	#[DataProvider( 'dataMixedValues' )]
+	public function testAssertContainsNotOnlyClosedResourceSucceeds( $haystack ) {
+		self::assertContainsNotOnlyClosedResource( $haystack );
+	}
+
+	/**
+	 * Verify the assertContainsNotOnlyClosedResource() method fails on invalid input.
+	 *
+	 * @return void
+	 */
+	public function testAssertContainsNotOnlyClosedResourceFails() {
+		$pattern = '`(\]|\)) does not contain only values of type "resource \(closed\)"\.`';
+
+		$this->expectException( AssertionFailedError::class );
+		$this->expectExceptionMessageMatches( $pattern );
+
+		self::assertContainsNotOnlyClosedResource( [ self::$closedResource ] );
+	}
+
+	/**
+	 * Verify that the assertContainsNotOnlyClosedResource() method fails a test with the correct custom failure message,
+	 * when the custom $message parameter has been passed.
+	 *
+	 * @return void
+	 */
+	public function testAssertContainsNotOnlyClosedResourceFailsWithCustomMessage() {
+		$pattern = '`^This assertion failed for reason XYZ\R`';
+
+		$this->expectException( AssertionFailedError::class );
+		$this->expectExceptionMessageMatches( $pattern );
+
+		self::assertContainsNotOnlyClosedResource( [ self::$closedResource ], 'This assertion failed for reason XYZ' );
+	}
+
+	/**
+	 * Verify the assertContainsOnlyScalar() method succeeds for valid input.
+	 *
+	 * @dataProvider dataOnlyScalar
+	 *
+	 * @param array<scalar> $haystack Haystack.
+	 *
+	 * @return void
+	 */
+	#[DataProvider( 'dataOnlyScalar' )]
+	public function testAssertContainsOnlyScalarSucceeds( $haystack ) {
+		self::assertContainsOnlyScalar( $haystack );
+	}
+
+	/**
+	 * Verify the assertContainsOnlyScalar() method fails on invalid input.
+	 *
+	 * @dataProvider dataMixedValues
+	 *
+	 * @param array<mixed> $haystack Haystack.
+	 *
+	 * @return void
+	 */
+	#[DataProvider( 'dataMixedValues' )]
+	public function testAssertContainsOnlyScalarFails( $haystack ) {
+		$pattern = '`(\]|\)) contains only values of type "scalar"\.$`';
+
+		$this->expectException( AssertionFailedError::class );
+		$this->expectExceptionMessageMatches( $pattern );
+
+		$this->assertContainsOnlyScalar( $haystack );
+	}
+
+	/**
+	 * Verify the assertContainsNotOnlyScalar() method succeeds for valid input.
+	 *
+	 * @dataProvider dataMixedValues
+	 *
+	 * @param array<mixed> $haystack Haystack.
+	 *
+	 * @return void
+	 */
+	#[DataProvider( 'dataMixedValues' )]
+	public function testAssertContainsNotOnlyScalarSucceeds( $haystack ) {
+		static::assertContainsNotOnlyScalar( $haystack );
+	}
+
+	/**
+	 * Verify the assertContainsNotOnlyScalar() method fails on invalid input.
+	 *
+	 * @dataProvider dataOnlyScalar
+	 *
+	 * @param array<scalar> $haystack Haystack.
+	 *
+	 * @return void
+	 */
+	#[DataProvider( 'dataOnlyScalar' )]
+	public function testAssertContainsNotOnlyScalarFails( $haystack ) {
+		$pattern = '`(\]|\)) does not contain only values of type "scalar"\.$`';
+
+		$this->expectException( AssertionFailedError::class );
+		$this->expectExceptionMessageMatches( $pattern );
+
+		$this->assertContainsNotOnlyScalar( $haystack );
+	}
+
+	/**
+	 * Data provider.
+	 *
+	 * @return array<array<scalar>>
+	 */
+	public static function dataOnlyScalar() {
+		return [
+			'Array containing only scalars' => [
+				[
+					'string',
+					true,
+					10,
+					-1.3,
+					'',
+					0,
+				],
+			],
+		];
+	}
+
+	/**
+	 * Verify the assertContainsOnlyString() method succeeds for valid input.
+	 *
+	 * @dataProvider dataOnlyString
+	 *
+	 * @param array<string> $haystack Haystack.
+	 *
+	 * @return void
+	 */
+	#[DataProvider( 'dataOnlyString' )]
+	public function testAssertContainsOnlyStringSucceeds( $haystack ) {
+		static::assertContainsOnlyString( $haystack );
+	}
+
+	/**
+	 * Verify the assertContainsOnlyString() method fails on invalid input.
+	 *
+	 * @dataProvider dataMixedValues
+	 *
+	 * @param array<mixed> $haystack Haystack.
+	 *
+	 * @return void
+	 */
+	#[DataProvider( 'dataMixedValues' )]
+	public function testAssertContainsOnlyStringFails( $haystack ) {
+		$pattern = '`(\]|\)) contains only values of type "string"\.$`';
+
+		$this->expectException( AssertionFailedError::class );
+		$this->expectExceptionMessageMatches( $pattern );
+
+		$this->assertContainsOnlyString( $haystack );
+	}
+
+	/**
+	 * Verify the assertContainsNotOnlyString() method succeeds for valid input.
+	 *
+	 * @dataProvider dataMixedValues
+	 *
+	 * @param array<mixed> $haystack Haystack.
+	 *
+	 * @return void
+	 */
+	#[DataProvider( 'dataMixedValues' )]
+	public function testAssertContainsNotOnlyStringSucceeds( $haystack ) {
+		$this->assertContainsNotOnlyString( $haystack );
+	}
+
+	/**
+	 * Verify the assertContainsNotOnlyString() method fails on invalid input.
+	 *
+	 * @dataProvider dataOnlyString
+	 *
+	 * @param array<string> $haystack Haystack.
+	 *
+	 * @return void
+	 */
+	#[DataProvider( 'dataOnlyString' )]
+	public function testAssertContainsNotOnlyStringFails( $haystack ) {
+		$pattern = '`(\]|\)) does not contain only values of type "string"\.$`';
+
+		$this->expectException( AssertionFailedError::class );
+		$this->expectExceptionMessageMatches( $pattern );
+
+		self::assertContainsNotOnlyString( $haystack );
+	}
+
+	/**
+	 * Data provider.
+	 *
+	 * @return array<array<string>>
+	 */
+	public static function dataOnlyString() {
+		return [
+			'Array containing only strings' => [
+				[
+					'',
+					'foo',
+					'bar',
+					'baz',
+				],
+			],
+		];
+	}
+}
diff --git a/tests/TestCases/TestCaseTestTrait.php b/tests/TestCases/TestCaseTestTrait.php
index 50a0ca2..7e839e8 100644
--- a/tests/TestCases/TestCaseTestTrait.php
+++ b/tests/TestCases/TestCaseTestTrait.php
@@ -190,4 +190,13 @@ final public function testAvailabilityExpectUserDeprecation() {
 
 		\trigger_error( 'This is a deprecation notice', \E_USER_DEPRECATED );
 	}
+
+	/**
+	 * Verify availability of trait polyfilled PHPUnit methods [21].
+	 *
+	 * @return void
+	 */
+	final public function testAvailabilityAssertContainsOnly() {
+		$this->assertContainsOnlyBool( [ true, false ] );
+	}
 }