From ae328b975abcf34f7679c322cf5c8d84a6b3ae27 Mon Sep 17 00:00:00 2001 From: Neluxx Date: Wed, 26 Aug 2026 11:28:13 +0200 Subject: [PATCH 01/15] Check only own validation rule --- src/Traits/DataValidationTestTrait.php | 252 +++++++++++++++++-------- 1 file changed, 175 insertions(+), 77 deletions(-) diff --git a/src/Traits/DataValidationTestTrait.php b/src/Traits/DataValidationTestTrait.php index 8f128c2..a70d066 100644 --- a/src/Traits/DataValidationTestTrait.php +++ b/src/Traits/DataValidationTestTrait.php @@ -40,7 +40,7 @@ protected function testDataValidationNotEmpty( $list = [null, '']; $expected = ['_empty' => 'This field cannot be left empty']; - $this->testDataValidationInList($table, $list, $fieldName, $expected, $additionalDataSet, $options); + $this->testDataValidationInListContains($table, $list, $fieldName, $expected, $additionalDataSet, $options); } /** @@ -67,8 +67,8 @@ protected function testDataValidationEmpty( ): void { $list = [null, '']; - $expected = []; - $this->testDataValidationInList($table, $list, $fieldName, $expected, $additionalDataSet, $options); + $expected = ['_empty']; + $this->testDataValidationInListNotContains($table, $list, $fieldName, $expected, $additionalDataSet, $options); } /** @@ -88,7 +88,7 @@ protected function testDataValidationRequired( array $options = [], ): void { $expected = ['_required' => 'This field is required']; - $this->testDataValidation($table, $fieldName, $dataSet, $expected, $options); + $this->testDataValidationContains($table, $fieldName, $dataSet, $expected, $options); } /** @@ -107,7 +107,8 @@ protected function testDataValidationNotRequired( array $dataSet = [], array $options = [], ): void { - $this->testDataValidationNoErrors($table, $fieldName, $dataSet, $options); + $expected = ['_required']; + $this->testDataValidationNotContains($table, $fieldName, $dataSet, $expected, $options); } /** @@ -128,13 +129,13 @@ protected function testDataValidationBoolean( ): void { // Valid values $list = [true, false, 1, 0]; - $expected = []; - $this->testDataValidationInList($table, $list, $fieldName, $expected, $additionalDataSet, $options); + $expected = ['boolean']; + $this->testDataValidationInListNotContains($table, $list, $fieldName, $expected, $additionalDataSet, $options); // Invalid values $list = ['Not a boolean', 123, []]; $expected = ['boolean' => 'The provided value must be a boolean']; - $this->testDataValidationInList($table, $list, $fieldName, $expected, $additionalDataSet, $options); + $this->testDataValidationInListContains($table, $list, $fieldName, $expected, $additionalDataSet, $options); } /** @@ -155,13 +156,13 @@ protected function testDataValidationURLWithProtocol( ): void { // Valid values $list = ['https://valid.com', 'http://valid.com']; - $expected = []; - $this->testDataValidationInList($table, $list, $fieldName, $expected, $additionalDataSet, $options); + $expected = ['urlWithProtocol']; + $this->testDataValidationInListNotContains($table, $list, $fieldName, $expected, $additionalDataSet, $options); // Invalid values $list = ['no-protocol.com', 'htp://foo.com']; $expected = ['urlWithProtocol' => 'The provided value must be a URL with protocol']; - $this->testDataValidationInList($table, $list, $fieldName, $expected, $additionalDataSet, $options); + $this->testDataValidationInListContains($table, $list, $fieldName, $expected, $additionalDataSet, $options); } /** @@ -188,8 +189,8 @@ protected function testDataValidationDateTime( new Chronos(), new FrozenTime(), ]; - $expected = []; - $this->testDataValidationInList($table, $list, $fieldName, $expected, $additionalDataSet, $options); + $expected = ['dateTime']; + $this->testDataValidationInListNotContains($table, $list, $fieldName, $expected, $additionalDataSet, $options); // Invalid values $list = [ @@ -200,7 +201,7 @@ protected function testDataValidationDateTime( '123', // Numeric ]; $expected = ['dateTime' => 'The provided value must be a date and time of one of these formats: `ymd`']; - $this->testDataValidationInList($table, $list, $fieldName, $expected, $additionalDataSet, $options); + $this->testDataValidationInListContains($table, $list, $fieldName, $expected, $additionalDataSet, $options); } /** @@ -229,8 +230,8 @@ protected function testDataValidationDate( new FrozenDate(), new FrozenTime(), ]; - $expected = []; - $this->testDataValidationInList($table, $list, $fieldName, $expected, $additionalDataSet, $options); + $expected = ['date']; + $this->testDataValidationInListNotContains($table, $list, $fieldName, $expected, $additionalDataSet, $options); // Invalid values $list = [ @@ -241,7 +242,7 @@ protected function testDataValidationDate( $expected = [ 'date' => 'The provided value must be a date of one of these formats: `ymd`', ]; - $this->testDataValidationInList($table, $list, $fieldName, $expected, $additionalDataSet, $options); + $this->testDataValidationInListContains($table, $list, $fieldName, $expected, $additionalDataSet, $options); } /** @@ -351,6 +352,53 @@ protected function testDataValidationContains( $entity = $table->newEntity($dataSet, $options); $errors = $entity->getError($fieldName); + $this->assertDataValidationErrorsContain($fieldName, $errors, $expected); + } + + /** + * Validate that a field's data validation error array does NOT contain the given rule(s) + * + * Other validation errors will be ignored. + * + * @param Table $table The table to test. + * @param string $fieldName The field to check for data validation errors. + * @param array $dataSet The data set to test. + * @param array $rules The rule names ("rule name") that must NOT be present. + * @param array $options Additional options for newEntity. + * @return void + * @see \Cake\Validation\Validator::validate() + */ + protected function testDataValidationNotContains( + Table $table, + string $fieldName, + array $dataSet, + array $rules, + array $options = [], + ): void { + $entity = $table->newEntity($dataSet, $options); + $errors = $entity->getError($fieldName); + + foreach ($rules as $rule) { + static::assertArrayNotHasKey($rule, $errors, sprintf( + 'Field `%s` has unexpected validation error `%s`.', + $fieldName, + $rule, + )); + } + } + + /** + * Assert that a given field errors array contains the expected "rule name" => "message" pair(s) + * + * Other validation errors are ignored. + * + * @param string $fieldName The field the errors belong to (used for assertion messages). + * @param array $errors The field's validation errors. + * @param array $expected The expected errors ("rule name" => "message") that must be present. + * @return void + */ + protected function assertDataValidationErrorsContain(string $fieldName, array $errors, array $expected): void + { foreach ($expected as $rule => $message) { static::assertArrayHasKey($rule, $errors, sprintf( 'Field `%s` does not have expected validation error `%s`.', @@ -365,6 +413,60 @@ protected function testDataValidationContains( } } + /** + * Validate that each value of a list of values leads to a field error containing the expected rule(s) + * + * Other validation errors will be ignored. + * + * @param Table $table The table to test. + * @param array $list A list of values to test. + * @param string $fieldName The field to check for data validation errors. + * @param array $expected The expected errors ("rule name" => "message") that must be present. + * @param array $additionalDataSet Additional data set to test. + * @param array $options Additional options for newEntity. + * @return void + */ + protected function testDataValidationInListContains( + Table $table, + array $list, + string $fieldName, + array $expected = [], + array $additionalDataSet = [], + array $options = [], + ): void { + foreach ($list as $value) { + $dataSet = array_merge($additionalDataSet, [$fieldName => $value]); + $this->testDataValidationContains($table, $fieldName, $dataSet, $expected, $options); + } + } + + /** + * Validate that each value of a list of values does NOT lead to a field error for the given rule(s) + * + * Other validation errors will be ignored. + * + * @param Table $table The table to test. + * @param array $list A list of values to test. + * @param string $fieldName The field to check for data validation errors. + * @param array $rules The rule names ("rule name") that must NOT be present. + * @param array $additionalDataSet Additional data set to test. + * @param array $options Additional options for newEntity. + * @return void + */ + protected function testDataValidationInListNotContains( + Table $table, + array $list, + string $fieldName, + array $rules = [], + array $additionalDataSet = [], + array $options = [], + ): void { + foreach ($list as $value) { + $dataSet = array_merge($additionalDataSet, [$fieldName => $value]); + $this->testDataValidationNotContains($table, $fieldName, $dataSet, $rules, $options); + } + } + /** * Validate that a given data set for a given table leads to the expected rule errors * @@ -464,10 +566,8 @@ protected function testDataValidationMaxLength( $tooLongFieldContent = str_repeat('A', $maxLength + 1); $dataset = [$fieldName => $tooLongFieldContent]; - $expected ??= [ - 'maxLength' => sprintf('The provided value must be at most `%d` characters long', $maxLength), - ]; - $this->testDataValidation($table, $fieldName, $dataset, $expected, $options); + $expected ??= ['maxLength' => sprintf('The provided value must be at most `%d` characters long', $maxLength)]; + $this->testDataValidationContains($table, $fieldName, $dataset, $expected, $options); } /** @@ -490,10 +590,8 @@ protected function testDataValidationMinLength( ): void { $tooShortFieldContent = str_repeat('A', $minLength - 1); $dataset = [$fieldName => $tooShortFieldContent]; - $expected ??= [ - 'minLength' => sprintf('The provided value must be at least `%d` characters long', $minLength), - ]; - $this->testDataValidation($table, $fieldName, $dataset, $expected, $options); + $expected ??= ['minLength' => sprintf('The provided value must be at least `%d` characters long', $minLength)]; + $this->testDataValidationContains($table, $fieldName, $dataset, $expected, $options); } /** @@ -548,12 +646,12 @@ protected function testDataValidationDecimal( $expected ??= [ 'decimal' => 'The provided value must be decimal with any number of decimal places, including none', ]; - $this->testDataValidationInList($table, $list, $fieldName, $expected, [], $options); + $this->testDataValidationInListContains($table, $list, $fieldName, $expected, [], $options); // Valid values $list = [-99.0, 0.099]; - $expected = []; - $this->testDataValidationInList($table, $list, $fieldName, $expected, [], $options); + $expected = ['decimal']; + $this->testDataValidationInListNotContains($table, $list, $fieldName, $expected, [], $options); } /** @@ -584,12 +682,12 @@ protected function testDataValidationInteger( 'ab0,099', ]; $expected ??= ['integer' => 'The provided value must be an integer']; - $this->testDataValidationInList($table, $list, $fieldName, $expected, [], $options); + $this->testDataValidationInListContains($table, $list, $fieldName, $expected, [], $options); // Valid values $list = [-99, 99]; - $expected = []; - $this->testDataValidationInList($table, $list, $fieldName, $expected, [], $options); + $expected = ['integer']; + $this->testDataValidationInListNotContains($table, $list, $fieldName, $expected, [], $options); } /** @@ -610,16 +708,15 @@ protected function testDataValidationNonNegativeInteger( ): void { // Negative integer $dataset = [$fieldName => '-1']; - $expected ??= [ - 'nonNegativeInteger' => 'The provided value must be a non-negative integer', - ]; + $expected ??= ['nonNegativeInteger' => 'The provided value must be a non-negative integer']; - $this->testDataValidation($table, $fieldName, $dataset, $expected, $options); + $this->testDataValidationContains($table, $fieldName, $dataset, $expected, $options); // Non-negative integer $dataset = [$fieldName => '0']; - $this->testDataValidationNoErrors($table, $fieldName, $dataset, $options); + $expected = ['nonNegativeInteger']; + $this->testDataValidationNotContains($table, $fieldName, $dataset, $expected, $options); } /** @@ -654,12 +751,13 @@ protected function testDataValidationGreaterThanOrEqual( $threshold, ), ]; - $this->testDataValidation($table, $fieldName, $dataset, $expected, $options); + $this->testDataValidationContains($table, $fieldName, $dataset, $expected, $options); // Valid values: exactly at and just above the threshold $aboveThreshold = is_int($threshold) ? $threshold + 1 : $threshold + 0.01; $list = [$threshold, $aboveThreshold]; - $this->testDataValidationInList($table, $list, $fieldName, [], $additionalDataSet, $options); + $expected = ['greaterThanOrEqual']; + $this->testDataValidationInListNotContains($table, $list, $fieldName, $expected, $additionalDataSet, $options); } /** @@ -678,15 +776,6 @@ protected function testDataValidationEmail( ?array $expected = null, ?array $options = [], ): void { - // Valid values - $list = [ - 'valid@email.test', - 'VALID@EMAIL.TEST', - 'va_lid.123@email.test', - 'va_lid.123+spamfolder@email.test', - ]; - $this->testDataValidationInList($table, $list, $fieldName, [], [], $options); - // Invalid values $list = [ 'invalid', @@ -694,10 +783,18 @@ protected function testDataValidationEmail( 'in.valid', 'in@valid.1', ]; - $expected ??= [ - 'email' => 'The provided value must be an e-mail address', + $expected ??= ['email' => 'The provided value must be an e-mail address']; + $this->testDataValidationInListContains($table, $list, $fieldName, $expected, [], $options); + + // Valid values + $list = [ + 'valid@email.test', + 'VALID@EMAIL.TEST', + 'va_lid.123@email.test', + 'va_lid.123+spamfolder@email.test', ]; - $this->testDataValidationInList($table, $list, $fieldName, $expected, [], $options); + $expected = ['email']; + $this->testDataValidationInListNotContains($table, $list, $fieldName, $expected, [], $options); } /** @@ -716,6 +813,16 @@ protected function testDataValidationUuid( ?array $expected = null, ?array $options = [], ): void { + // Invalid values + $list = [ + 'c232ay00-9414-11ec-b3c8-9f6bdeced846', // Invalid Hexadecimal value "y" + 'c232aa00-941-11ec4-b3c-89f6bdeced846', // Correct length wrong format + '5df41881-3aed-3515-88a7-2f4a814cf09', // Too short + 'notAUuid', // Not a UUID + ]; + $expected ??= ['uuid' => 'The provided value must be a UUID']; + $this->testDataValidationInListContains($table, $list, $fieldName, $expected, [], $options); + // Valid values $list = [ 'e22e1622-5c14-11ea-b2f3-0242ac130003', // UUID v1 @@ -728,19 +835,8 @@ protected function testDataValidationUuid( '00112233-4455-8677-8899-aabbccddeeff', // UUID v8 'fc93ab0e-c99e-4b58-975e-9c5e68c53624', // GUID ]; - $this->testDataValidationInList($table, $list, $fieldName, [], [], $options); - - // Invalid values - $list = [ - 'c232ay00-9414-11ec-b3c8-9f6bdeced846', // Invalid Hexadecimal value "y" - 'c232aa00-941-11ec4-b3c-89f6bdeced846', // Correct length wrong format - '5df41881-3aed-3515-88a7-2f4a814cf09', // Too short - 'notAUuid', // Not a UUID - ]; - $expected ??= [ - 'uuid' => 'The provided value must be a UUID', - ]; - $this->testDataValidationInList($table, $list, $fieldName, $expected, [], $options); + $expected = ['uuid']; + $this->testDataValidationInListNotContains($table, $list, $fieldName, $expected, [], $options); } /** @@ -775,7 +871,7 @@ protected function testDataValidationLengthBetween( $maxlength, ), ]; - $this->testDataValidation($table, $fieldName, $dataset, $expected, $options); + $this->testDataValidationContains($table, $fieldName, $dataset, $expected, $options); } // Too long @@ -789,7 +885,7 @@ protected function testDataValidationLengthBetween( $maxlength, ), ]; - $this->testDataValidation($table, $fieldName, $dataset, $expected, $options); + $this->testDataValidationContains($table, $fieldName, $dataset, $expected, $options); } /** @@ -808,15 +904,15 @@ protected function testDataValidationNaturalNumber( array $additionalDataSet = [], array $options = [], ): void { - // Valid value - $list = [1]; - $expected = []; - $this->testDataValidationInList($table, $list, $fieldName, $expected, $additionalDataSet, $options); - // Invalid values $list = [0, -1]; $expected = ['naturalNumber' => 'The provided value must be a natural number']; - $this->testDataValidationInList($table, $list, $fieldName, $expected, $additionalDataSet, $options); + $this->testDataValidationInListContains($table, $list, $fieldName, $expected, $additionalDataSet, $options); + + // Valid value + $list = [1]; + $expected = ['naturalNumber']; + $this->testDataValidationInListNotContains($table, $list, $fieldName, $expected, $additionalDataSet, $options); } /** @@ -843,23 +939,24 @@ protected function testFullDataValidation(Table $table, array $dataSet, array $e * @param Table $table The table to test. * @param string $fieldName The field to check the foreign key for. * @param int|null $notExistingForeignKey Not existing foreign key. Defaults to 999999. + * @param array|null $expected The expected rule errors ("rule name" => "message") that must be present. * @return void */ protected function testDataValidationForeignKey( Table $table, string $fieldName, ?int $notExistingForeignKey = 999999, + ?array $expected = null, ): void { $entity = $table->newEmptyEntity(); $table->patchEntity($entity, [$fieldName => $notExistingForeignKey]); $result = $table->checkRules($entity); static::assertFalse($result); - static::assertNotNull($entity->getErrors()[$fieldName]); - $expected = ['_existsIn' => 'This value does not exist']; + $expected ??= ['_existsIn' => 'This value does not exist']; - static::assertSame($expected, $entity->getErrors()[$fieldName]); + $this->assertDataValidationErrorsContain($fieldName, $entity->getError($fieldName), $expected); } /** @@ -869,6 +966,7 @@ protected function testDataValidationForeignKey( * @param string $fieldName The field to check the foreign key for. * @param mixed $fieldValue The field value. * @param array $additionalProperties Other required properties for the new entity. + * @param array|null $expected The expected rule errors ("rule name" => "message") that must be present. * @return void */ protected function testDataValidationIsUnique( @@ -876,6 +974,7 @@ protected function testDataValidationIsUnique( string $fieldName, mixed $fieldValue, array $additionalProperties = [], + ?array $expected = null, ): void { $prevEntity = $table->newEmptyEntity(); $table->patchEntity($prevEntity, array_merge($additionalProperties, [$fieldName => $fieldValue])); @@ -886,10 +985,9 @@ protected function testDataValidationIsUnique( $result = $table->checkRules($entity); static::assertFalse($result); - static::assertNotNull($entity->getErrors()[$fieldName]); - $expected = ['_isUnique' => 'This value is already in use']; + $expected ??= ['_isUnique' => 'This value is already in use']; - static::assertSame($expected, $entity->getErrors()[$fieldName]); + $this->assertDataValidationErrorsContain($fieldName, $entity->getError($fieldName), $expected); } } From e4fcb67120f58cf39f6a915c25a58d0d5d3be99c Mon Sep 17 00:00:00 2001 From: Neluxx Date: Wed, 26 Aug 2026 11:28:41 +0200 Subject: [PATCH 02/15] Add unit tests for new data validation helper methods --- .../Traits/DataValidationTestTraitTest.php | 103 +++++++++++++++++- 1 file changed, 101 insertions(+), 2 deletions(-) diff --git a/tests/TestCase/Traits/DataValidationTestTraitTest.php b/tests/TestCase/Traits/DataValidationTestTraitTest.php index e415bef..e069450 100644 --- a/tests/TestCase/Traits/DataValidationTestTraitTest.php +++ b/tests/TestCase/Traits/DataValidationTestTraitTest.php @@ -275,6 +275,7 @@ public function testTestDataValidationMinLength(): void * * @return void * @covers ::testDataValidationScalar + * @covers ::testDataValidationContains */ public function testTestDataValidationScalar(): void { @@ -509,13 +510,13 @@ public function testTestDataRulesNoErrors(): void { // Ensure the rule works as expected first $field = 'unique_field'; - $dataSet = [$field => 'unique-value-' . uniqid()]; + $dataSet = [$field => 'unique-value-' . uniqid('', true)]; $entity = $this->table->newEntity($dataSet, ['validate' => false]); static::assertNotFalse($this->table->save($entity)); static::assertEmpty($entity->getError($field)); - $this->testDataRulesNoErrors($this->table, $field, [$field => 'another-unique-' . uniqid()]); + $this->testDataRulesNoErrors($this->table, $field, [$field => 'another-unique-' . uniqid('', true)]); } /** @@ -603,4 +604,102 @@ public function testTestRules(): void $this->testRules($this->table, $field, $dataSet, $expectedErrors); } + + /** + * Test the testDataValidationNotContains base method. + * + * @return void + * @covers ::testDataValidationNotContains + */ + public function testTestDataValidationNotContains(): void + { + // A valid boolean value must not produce a `boolean` error + $field = 'boolean_field'; + $entity = $this->table->newEntity([$field => true]); + static::assertArrayNotHasKey('boolean', $entity->getError($field)); + + $this->testDataValidationNotContains($this->table, $field, [$field => true], ['boolean']); + } + + /** + * Test the testDataValidationInListContains base method. + * + * @return void + * @covers ::testDataValidationInListContains + * @covers ::assertDataValidationErrorsContain + */ + public function testTestDataValidationInListContains(): void + { + $field = 'boolean_field'; + $expectedErrors = ['boolean' => 'The provided value must be a boolean']; + $invalidValues = ['Not a boolean', 123]; + + $this->testDataValidationInListContains($this->table, $invalidValues, $field, $expectedErrors); + } + + /** + * Test the testDataValidationInListNotContains base method. + * + * @return void + * @covers ::testDataValidationInListNotContains + */ + public function testTestDataValidationInListNotContains(): void + { + $field = 'boolean_field'; + $validValues = [true, false, 1, 0]; + + $this->testDataValidationInListNotContains($this->table, $validValues, $field, ['boolean']); + } + + /** + * Test that a type-specific method only asserts its own rule and ignores unrelated errors. + * + * @return void + * @covers ::testDataValidationInteger + */ + public function testTypeSpecificMethodIgnoresUnrelatedErrors(): void + { + $field = 'multi_rule_field'; + + // The invalid value triggers both the integer and the (unrelated) maxLength rule + $entity = $this->table->newEntity([$field => 'abcd']); + $errors = $entity->getError($field); + static::assertArrayHasKey('integer', $errors); + static::assertArrayHasKey('maxLength', $errors); + + // The type-specific method still passes because it only checks its own `integer` rule + $this->testDataValidationInteger($this->table, $field); + } + + /** + * Test that testDataValidationForeignKey accepts a custom expected error. + * + * @return void + * @covers ::testDataValidationForeignKey + */ + public function testTestDataValidationForeignKeyCustomExpected(): void + { + $field = 'parent_id'; + $expectedErrors = ['_existsIn' => 'This value does not exist']; + + $this->testDataValidationForeignKey($this->table, $field, 999999, $expectedErrors); + } + + /** + * Test that testDataValidationIsUnique accepts a custom expected error. + * + * @return void + * @covers ::testDataValidationIsUnique + */ + public function testTestDataValidationIsUniqueCustomExpected(): void + { + $field = 'unique_field'; + $dataset = [ + $field => 'custom-duplicate-value', + 'required_field' => 'required', + ]; + $expectedErrors = ['_isUnique' => 'This value is already in use']; + + $this->testDataValidationIsUnique($this->table, $field, 'custom-duplicate-value', $dataset, $expectedErrors); + } } From 9ae88fc4cc71bc5b209e9ebbd46c554462d8b254 Mon Sep 17 00:00:00 2001 From: Neluxx Date: Wed, 26 Aug 2026 11:29:43 +0200 Subject: [PATCH 03/15] Add multi_rule_field to validation_test table --- tests/TestApp/Model/Table/ValidationTestTable.php | 7 ++++++- tests/bootstrap.php | 3 ++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/tests/TestApp/Model/Table/ValidationTestTable.php b/tests/TestApp/Model/Table/ValidationTestTable.php index 49e18dd..ae345d5 100644 --- a/tests/TestApp/Model/Table/ValidationTestTable.php +++ b/tests/TestApp/Model/Table/ValidationTestTable.php @@ -65,7 +65,12 @@ public function validationDefault(Validator $validator): Validator ->allowEmptyString('parent_id') ->integer('parent_id') ->email('email_field') - ->uuid('uuid_field'); + ->uuid('uuid_field') + // A field with more than one validation rule, used to assert that the + // type-specific trait methods only check their own rule and ignore others. + ->integer('multi_rule_field') + ->maxLength('multi_rule_field', 3) + ->allowEmptyString('multi_rule_field'); return $validator; } diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 8dfa39f..180a94a 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -82,7 +82,8 @@ natural_number_field INTEGER, unique_field VARCHAR(255) UNIQUE, email_field VARCHAR(255), - uuid VARCHAR(36) + uuid VARCHAR(36), + multi_rule_field VARCHAR(255) ) '); From 2cd35491855253041ad346e26275d1b0dfcb2f94 Mon Sep 17 00:00:00 2001 From: Neluxx Date: Wed, 26 Aug 2026 11:37:37 +0200 Subject: [PATCH 04/15] Update usage guide --- docs/Usage.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/Usage.md b/docs/Usage.md index 3558e43..936cdd1 100644 --- a/docs/Usage.md +++ b/docs/Usage.md @@ -83,9 +83,13 @@ Each helper builds an entity, runs the validator, and asserts the expected error ### Generic helpers -- `testDataValidation($table, $fieldName, $dataSet, $expected)` - the underlying helper. Use when no specialized helper fits. +- `testDataValidation($table, $fieldName, $dataSet, $expected)` - the underlying helper. Use when no specialized helper fits. Compares the field's **complete** error array against `$expected`. +- `testDataValidationContains($table, $fieldName, $dataSet, $expected)` - asserts the given `"rule name" => "message"` pairs are present on the field, ignoring any other errors. +- `testDataValidationNotContains($table, $fieldName, $dataSet, $rules)` - asserts the given rule names are **not** present on the field, ignoring any other errors. - `testDataValidationNoErrors($table, $fieldName, $dataSet)` - asserts a data set produces no errors on the field. -- `testDataValidationInList($table, $list, $fieldName, $expected)` - runs the same assertion for each value in a list. +- `testDataValidationInList($table, $list, $fieldName, $expected)` - runs the complete-error-array assertion for each value in a list. +- `testDataValidationInListContains($table, $list, $fieldName, $expected)` - runs the `contains` assertion for each value in a list. +- `testDataValidationInListNotContains($table, $list, $fieldName, $rules)` - runs the `not contains` assertion for each value in a list. - `testFullDataValidation($table, $dataSet, $expected)` - asserts errors across all fields. - `testFullDataValidationNoErrors($table, $dataSet)` - asserts a full data set produces no errors at all. From b5d9280c7b8554a142e5ec1f1b40f8060439ff51 Mon Sep 17 00:00:00 2001 From: Neluxx Date: Wed, 26 Aug 2026 11:38:48 +0200 Subject: [PATCH 05/15] Update change log --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c23ea4..b2429fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,8 +6,12 @@ This project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased](https://github.com/orca-services/cakephp-data-validation-testing) ### Added +- `testDataValidationNotContains()` to assert that specific validation rules are absent, ignoring others on the same field. +- `testDataValidationInListContains()` and `testDataValidationInListNotContains()` list helpers. +- Optional custom `$expected` parameter for `testDataValidationForeignKey()` and `testDataValidationIsUnique()`. ### Changed +- **BREAKING CHANGE:** All type-specific/rule-dedicated methods now assert only their own validation rule for the field. ### Fixed From 61af2691461be36c4ac4a9a095aefbdb56d47d05 Mon Sep 17 00:00:00 2001 From: "maurin.stutz" Date: Wed, 26 Aug 2026 15:10:24 +0200 Subject: [PATCH 06/15] Improve parameter documentation --- src/Traits/DataValidationTestTrait.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Traits/DataValidationTestTrait.php b/src/Traits/DataValidationTestTrait.php index a70d066..da0f6df 100644 --- a/src/Traits/DataValidationTestTrait.php +++ b/src/Traits/DataValidationTestTrait.php @@ -960,11 +960,11 @@ protected function testDataValidationForeignKey( } /** - * Validate the uniq value of the given field + * Validate the unique value of the given field * * @param Table $table The table to test. * @param string $fieldName The field to check the foreign key for. - * @param mixed $fieldValue The field value. + * @param mixed $fieldValue The field value which will be saved twice (must be unique). * @param array $additionalProperties Other required properties for the new entity. * @param array|null $expected The expected rule errors ("rule name" => "message") that must be present. * @return void From 48634c84eb76aef22516eb9188c2cc1c8b6e9748 Mon Sep 17 00:00:00 2001 From: "maurin.stutz" Date: Thu, 27 Aug 2026 08:39:14 +0200 Subject: [PATCH 07/15] Ignore dataValidation in `testDataValidationIsUnique()` to correctly assert build rules. --- CHANGELOG.md | 1 + src/Traits/DataValidationTestTrait.php | 12 ++++++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b2429fd..fca358d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). - **BREAKING CHANGE:** All type-specific/rule-dedicated methods now assert only their own validation rule for the field. ### Fixed +- Ignore dataValidation in `testDataValidationIsUnique()` to correctly assert build rules. ### Dependencies diff --git a/src/Traits/DataValidationTestTrait.php b/src/Traits/DataValidationTestTrait.php index da0f6df..577eaeb 100644 --- a/src/Traits/DataValidationTestTrait.php +++ b/src/Traits/DataValidationTestTrait.php @@ -977,11 +977,19 @@ protected function testDataValidationIsUnique( ?array $expected = null, ): void { $prevEntity = $table->newEmptyEntity(); - $table->patchEntity($prevEntity, array_merge($additionalProperties, [$fieldName => $fieldValue])); + $table->patchEntity( + $prevEntity, + array_merge($additionalProperties, [$fieldName => $fieldValue]), + ['validate' => false], + ); $table->saveOrFail($prevEntity); $entity = $table->newEmptyEntity(); - $table->patchEntity($entity, array_merge($additionalProperties, [$fieldName => $fieldValue])); + $table->patchEntity( + $entity, + array_merge($additionalProperties, [$fieldName => $fieldValue]), + ['validate' => false], + ); $result = $table->checkRules($entity); static::assertFalse($result); From f384526d2316982486d9de629bd5024f7c7c7790 Mon Sep 17 00:00:00 2001 From: Fabian Arndt Date: Thu, 27 Aug 2026 13:35:34 +0200 Subject: [PATCH 08/15] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Marc Würth --- CHANGELOG.md | 2 +- tests/TestApp/Model/Table/ValidationTestTable.php | 1 + tests/TestCase/Traits/DataValidationTestTraitTest.php | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fca358d..efd78e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). - Optional custom `$expected` parameter for `testDataValidationForeignKey()` and `testDataValidationIsUnique()`. ### Changed -- **BREAKING CHANGE:** All type-specific/rule-dedicated methods now assert only their own validation rule for the field. +- **BREAKING CHANGE:** All type-specific/rule-dedicated methods now assert only their own validation rule for the field, ignoring others. ### Fixed - Ignore dataValidation in `testDataValidationIsUnique()` to correctly assert build rules. diff --git a/tests/TestApp/Model/Table/ValidationTestTable.php b/tests/TestApp/Model/Table/ValidationTestTable.php index ae345d5..4f6455f 100644 --- a/tests/TestApp/Model/Table/ValidationTestTable.php +++ b/tests/TestApp/Model/Table/ValidationTestTable.php @@ -68,6 +68,7 @@ public function validationDefault(Validator $validator): Validator ->uuid('uuid_field') // A field with more than one validation rule, used to assert that the // type-specific trait methods only check their own rule and ignore others. + ->requirePresence('multi_rule_field') ->integer('multi_rule_field') ->maxLength('multi_rule_field', 3) ->allowEmptyString('multi_rule_field'); diff --git a/tests/TestCase/Traits/DataValidationTestTraitTest.php b/tests/TestCase/Traits/DataValidationTestTraitTest.php index e069450..f62ebe8 100644 --- a/tests/TestCase/Traits/DataValidationTestTraitTest.php +++ b/tests/TestCase/Traits/DataValidationTestTraitTest.php @@ -667,7 +667,7 @@ public function testTypeSpecificMethodIgnoresUnrelatedErrors(): void static::assertArrayHasKey('integer', $errors); static::assertArrayHasKey('maxLength', $errors); - // The type-specific method still passes because it only checks its own `integer` rule + // The type-specific method still passes because it checks for the `integer` rule, only $this->testDataValidationInteger($this->table, $field); } From 1ef92414982cae29ab4a6d5512919f4bac63b01e Mon Sep 17 00:00:00 2001 From: Neluxx Date: Thu, 27 Aug 2026 14:43:45 +0200 Subject: [PATCH 09/15] Rename $expected to $notExpected in rule-dedicated methods --- src/Traits/DataValidationTestTrait.php | 101 ++++++++++++++++++------- 1 file changed, 75 insertions(+), 26 deletions(-) diff --git a/src/Traits/DataValidationTestTrait.php b/src/Traits/DataValidationTestTrait.php index 577eaeb..93f5c39 100644 --- a/src/Traits/DataValidationTestTrait.php +++ b/src/Traits/DataValidationTestTrait.php @@ -67,8 +67,15 @@ protected function testDataValidationEmpty( ): void { $list = [null, '']; - $expected = ['_empty']; - $this->testDataValidationInListNotContains($table, $list, $fieldName, $expected, $additionalDataSet, $options); + $notExpected = ['_empty']; + $this->testDataValidationInListNotContains( + $table, + $list, + $fieldName, + $notExpected, + $additionalDataSet, + $options, + ); } /** @@ -107,8 +114,8 @@ protected function testDataValidationNotRequired( array $dataSet = [], array $options = [], ): void { - $expected = ['_required']; - $this->testDataValidationNotContains($table, $fieldName, $dataSet, $expected, $options); + $notExpected = ['_required']; + $this->testDataValidationNotContains($table, $fieldName, $dataSet, $notExpected, $options); } /** @@ -129,8 +136,15 @@ protected function testDataValidationBoolean( ): void { // Valid values $list = [true, false, 1, 0]; - $expected = ['boolean']; - $this->testDataValidationInListNotContains($table, $list, $fieldName, $expected, $additionalDataSet, $options); + $notExpected = ['boolean']; + $this->testDataValidationInListNotContains( + $table, + $list, + $fieldName, + $notExpected, + $additionalDataSet, + $options, + ); // Invalid values $list = ['Not a boolean', 123, []]; @@ -156,8 +170,15 @@ protected function testDataValidationURLWithProtocol( ): void { // Valid values $list = ['https://valid.com', 'http://valid.com']; - $expected = ['urlWithProtocol']; - $this->testDataValidationInListNotContains($table, $list, $fieldName, $expected, $additionalDataSet, $options); + $notExpected = ['urlWithProtocol']; + $this->testDataValidationInListNotContains( + $table, + $list, + $fieldName, + $notExpected, + $additionalDataSet, + $options, + ); // Invalid values $list = ['no-protocol.com', 'htp://foo.com']; @@ -189,8 +210,15 @@ protected function testDataValidationDateTime( new Chronos(), new FrozenTime(), ]; - $expected = ['dateTime']; - $this->testDataValidationInListNotContains($table, $list, $fieldName, $expected, $additionalDataSet, $options); + $notExpected = ['dateTime']; + $this->testDataValidationInListNotContains( + $table, + $list, + $fieldName, + $notExpected, + $additionalDataSet, + $options, + ); // Invalid values $list = [ @@ -230,8 +258,15 @@ protected function testDataValidationDate( new FrozenDate(), new FrozenTime(), ]; - $expected = ['date']; - $this->testDataValidationInListNotContains($table, $list, $fieldName, $expected, $additionalDataSet, $options); + $notExpected = ['date']; + $this->testDataValidationInListNotContains( + $table, + $list, + $fieldName, + $notExpected, + $additionalDataSet, + $options, + ); // Invalid values $list = [ @@ -650,8 +685,8 @@ protected function testDataValidationDecimal( // Valid values $list = [-99.0, 0.099]; - $expected = ['decimal']; - $this->testDataValidationInListNotContains($table, $list, $fieldName, $expected, [], $options); + $notExpected = ['decimal']; + $this->testDataValidationInListNotContains($table, $list, $fieldName, $notExpected, [], $options); } /** @@ -686,8 +721,8 @@ protected function testDataValidationInteger( // Valid values $list = [-99, 99]; - $expected = ['integer']; - $this->testDataValidationInListNotContains($table, $list, $fieldName, $expected, [], $options); + $notExpected = ['integer']; + $this->testDataValidationInListNotContains($table, $list, $fieldName, $notExpected, [], $options); } /** @@ -715,8 +750,8 @@ protected function testDataValidationNonNegativeInteger( // Non-negative integer $dataset = [$fieldName => '0']; - $expected = ['nonNegativeInteger']; - $this->testDataValidationNotContains($table, $fieldName, $dataset, $expected, $options); + $notExpected = ['nonNegativeInteger']; + $this->testDataValidationNotContains($table, $fieldName, $dataset, $notExpected, $options); } /** @@ -756,8 +791,15 @@ protected function testDataValidationGreaterThanOrEqual( // Valid values: exactly at and just above the threshold $aboveThreshold = is_int($threshold) ? $threshold + 1 : $threshold + 0.01; $list = [$threshold, $aboveThreshold]; - $expected = ['greaterThanOrEqual']; - $this->testDataValidationInListNotContains($table, $list, $fieldName, $expected, $additionalDataSet, $options); + $notExpected = ['greaterThanOrEqual']; + $this->testDataValidationInListNotContains( + $table, + $list, + $fieldName, + $notExpected, + $additionalDataSet, + $options, + ); } /** @@ -793,8 +835,8 @@ protected function testDataValidationEmail( 'va_lid.123@email.test', 'va_lid.123+spamfolder@email.test', ]; - $expected = ['email']; - $this->testDataValidationInListNotContains($table, $list, $fieldName, $expected, [], $options); + $notExpected = ['email']; + $this->testDataValidationInListNotContains($table, $list, $fieldName, $notExpected, [], $options); } /** @@ -835,8 +877,8 @@ protected function testDataValidationUuid( '00112233-4455-8677-8899-aabbccddeeff', // UUID v8 'fc93ab0e-c99e-4b58-975e-9c5e68c53624', // GUID ]; - $expected = ['uuid']; - $this->testDataValidationInListNotContains($table, $list, $fieldName, $expected, [], $options); + $notExpected = ['uuid']; + $this->testDataValidationInListNotContains($table, $list, $fieldName, $notExpected, [], $options); } /** @@ -911,8 +953,15 @@ protected function testDataValidationNaturalNumber( // Valid value $list = [1]; - $expected = ['naturalNumber']; - $this->testDataValidationInListNotContains($table, $list, $fieldName, $expected, $additionalDataSet, $options); + $notExpected = ['naturalNumber']; + $this->testDataValidationInListNotContains( + $table, + $list, + $fieldName, + $notExpected, + $additionalDataSet, + $options, + ); } /** From eda5a2879c5148891176299e5b6d5b027a3f6819 Mon Sep 17 00:00:00 2001 From: Neluxx Date: Thu, 27 Aug 2026 14:46:48 +0200 Subject: [PATCH 10/15] Reword type validator docs to describe the asserted rule --- docs/Usage.md | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/docs/Usage.md b/docs/Usage.md index 936cdd1..aa86a09 100644 --- a/docs/Usage.md +++ b/docs/Usage.md @@ -55,25 +55,25 @@ Each helper builds an entity, runs the validator, and asserts the expected error ### Presence and emptiness -- `testDataValidationRequired($table, $fieldName)` - field must be present. -- `testDataValidationNotRequired($table, $fieldName)` - field is optional. -- `testDataValidationNotEmpty($table, $fieldName)` - field cannot be `null` or `''`. -- `testDataValidationEmpty($table, $fieldName)` - field may be `null` or `''`. +- `testDataValidationRequired($table, $fieldName)` - asserts the field is configured to require presence (`_required`). +- `testDataValidationNotRequired($table, $fieldName)` - asserts the field is not configured to require presence. +- `testDataValidationNotEmpty($table, $fieldName)` - asserts the field is configured to disallow empty values, i.e. `null` or `''` (`_empty`). +- `testDataValidationEmpty($table, $fieldName)` - asserts the field is configured to allow empty values, i.e. `null` or `''`. ### Type validators -- `testDataValidationBoolean($table, $fieldName)` - field must be boolean. -- `testDataValidationURLWithProtocol($table, $fieldName)` - requires `http://` or `https://`. -- `testDataValidationDateTime($table, $fieldName)` - field must be datetime. -- `testDataValidationDate($table, $fieldName)` - field must be date. -- `testDataValidationNaturalNumber($table, $fieldName)` - positive integers only. -- `testDataValidationScalar($table, $fieldName)` - rejects non-scalar values like arrays. -- `testDataValidationDecimal($table, $fieldName)` - rejects non-decimal values like arrays. -- `testDataValidationInteger($table, $fieldName)` - rejects non-integer values like arrays. -- `testDataValidationNonNegativeInteger($table, $fieldName)` - rejects negative integer values like `-1`. -- `testDataValidationGreaterThanOrEqual($table, $fieldName)` - rejects values below the threshold. -- `testDataValidationEmail($table, $fieldName)` - rejects invalid email addresses. -- `testDataValidationUuid($table, $fieldName)` - rejects invalid UUIDs. +- `testDataValidationBoolean($table, $fieldName)` - asserts the field is configured with the `boolean` validation rule. +- `testDataValidationURLWithProtocol($table, $fieldName)` - asserts the field is configured with the `urlWithProtocol` validation rule (requires `http://` or `https://`). +- `testDataValidationDateTime($table, $fieldName)` - asserts the field is configured with the `dateTime` validation rule. +- `testDataValidationDate($table, $fieldName)` - asserts the field is configured with the `date` validation rule. +- `testDataValidationNaturalNumber($table, $fieldName)` - asserts the field is configured with the `naturalNumber` validation rule (positive integers). +- `testDataValidationScalar($table, $fieldName)` - asserts the field is configured with the `scalar` validation rule. +- `testDataValidationDecimal($table, $fieldName)` - asserts the field is configured with the `decimal` validation rule. +- `testDataValidationInteger($table, $fieldName)` - asserts the field is configured with the `integer` validation rule. +- `testDataValidationNonNegativeInteger($table, $fieldName)` - asserts the field is configured with the `nonNegativeInteger` validation rule. +- `testDataValidationGreaterThanOrEqual($table, $fieldName, $threshold)` - asserts the field is configured with the `greaterThanOrEqual` validation rule. +- `testDataValidationEmail($table, $fieldName)` - asserts the field is configured with the `email` validation rule. +- `testDataValidationUuid($table, $fieldName)` - asserts the field is configured with the `uuid` validation rule. ### Length validators From 03f2db8d133edacb137c277dd817032b7d4945af Mon Sep 17 00:00:00 2001 From: Neluxx Date: Thu, 27 Aug 2026 14:47:07 +0200 Subject: [PATCH 11/15] Note that rule-dedicated helpers assert only their own rule --- docs/Usage.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/Usage.md b/docs/Usage.md index aa86a09..d8f643a 100644 --- a/docs/Usage.md +++ b/docs/Usage.md @@ -53,6 +53,10 @@ Each helper builds an entity, runs the validator, and asserts the expected error ## Available methods +> **Note:** The presence, emptiness and type helpers listed below are rule-dedicated: each one only asserts that +> its own validation rule is (or is not) configured for the given field and ignores any other validation errors +> present on that field. They no longer compare against the field's full error list. + ### Presence and emptiness - `testDataValidationRequired($table, $fieldName)` - asserts the field is configured to require presence (`_required`). From 0a9b52dfad658bb9f60b221aa1ae0e4f769a1bc1 Mon Sep 17 00:00:00 2001 From: Neluxx Date: Thu, 27 Aug 2026 14:48:27 +0200 Subject: [PATCH 12/15] Group custom expected tests next to their sibling test cases --- .../Traits/DataValidationTestTraitTest.php | 64 +++++++++---------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/tests/TestCase/Traits/DataValidationTestTraitTest.php b/tests/TestCase/Traits/DataValidationTestTraitTest.php index f62ebe8..96e3d9f 100644 --- a/tests/TestCase/Traits/DataValidationTestTraitTest.php +++ b/tests/TestCase/Traits/DataValidationTestTraitTest.php @@ -551,6 +551,20 @@ public function testTestDataValidationForeignKeyDefault(): void $this->testDataValidationForeignKey($this->table, $field); } + /** + * Test that testDataValidationForeignKey accepts a custom expected error. + * + * @return void + * @covers ::testDataValidationForeignKey + */ + public function testTestDataValidationForeignKeyCustomExpected(): void + { + $field = 'parent_id'; + $expectedErrors = ['_existsIn' => 'This value does not exist']; + + $this->testDataValidationForeignKey($this->table, $field, 999999, $expectedErrors); + } + /** * Test that testDataValidationIsUnique passes when the field value is not unique. * @@ -580,6 +594,24 @@ public function testTestDataValidationIsUnique(): void $this->testDataValidationIsUnique($this->table, $field, 'another-duplicate-value', $dataset); } + /** + * Test that testDataValidationIsUnique accepts a custom expected error. + * + * @return void + * @covers ::testDataValidationIsUnique + */ + public function testTestDataValidationIsUniqueCustomExpected(): void + { + $field = 'unique_field'; + $dataset = [ + $field => 'custom-duplicate-value', + 'required_field' => 'required', + ]; + $expectedErrors = ['_isUnique' => 'This value is already in use']; + + $this->testDataValidationIsUnique($this->table, $field, 'custom-duplicate-value', $dataset, $expectedErrors); + } + /** * Test that testRules passes when saving leads to the expected rule errors. * @@ -670,36 +702,4 @@ public function testTypeSpecificMethodIgnoresUnrelatedErrors(): void // The type-specific method still passes because it checks for the `integer` rule, only $this->testDataValidationInteger($this->table, $field); } - - /** - * Test that testDataValidationForeignKey accepts a custom expected error. - * - * @return void - * @covers ::testDataValidationForeignKey - */ - public function testTestDataValidationForeignKeyCustomExpected(): void - { - $field = 'parent_id'; - $expectedErrors = ['_existsIn' => 'This value does not exist']; - - $this->testDataValidationForeignKey($this->table, $field, 999999, $expectedErrors); - } - - /** - * Test that testDataValidationIsUnique accepts a custom expected error. - * - * @return void - * @covers ::testDataValidationIsUnique - */ - public function testTestDataValidationIsUniqueCustomExpected(): void - { - $field = 'unique_field'; - $dataset = [ - $field => 'custom-duplicate-value', - 'required_field' => 'required', - ]; - $expectedErrors = ['_isUnique' => 'This value is already in use']; - - $this->testDataValidationIsUnique($this->table, $field, 'custom-duplicate-value', $dataset, $expectedErrors); - } } From a8b30e6353b0546066bc0407836a4f44ae112cb7 Mon Sep 17 00:00:00 2001 From: Neluxx Date: Thu, 27 Aug 2026 14:49:29 +0200 Subject: [PATCH 13/15] Add dedicated test for testDataValidationContains helper --- .../Traits/DataValidationTestTraitTest.php | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/tests/TestCase/Traits/DataValidationTestTraitTest.php b/tests/TestCase/Traits/DataValidationTestTraitTest.php index 96e3d9f..6b8c7fd 100644 --- a/tests/TestCase/Traits/DataValidationTestTraitTest.php +++ b/tests/TestCase/Traits/DataValidationTestTraitTest.php @@ -275,7 +275,6 @@ public function testTestDataValidationMinLength(): void * * @return void * @covers ::testDataValidationScalar - * @covers ::testDataValidationContains */ public function testTestDataValidationScalar(): void { @@ -637,6 +636,24 @@ public function testTestRules(): void $this->testRules($this->table, $field, $dataSet, $expectedErrors); } + /** + * Test the testDataValidationContains base method. + * + * @return void + * @covers ::testDataValidationContains + */ + public function testTestDataValidationContains(): void + { + // An invalid scalar value must produce a `scalar` error + $field = 'scalar_field'; + $expectedErrors = ['scalar' => 'The provided value must be scalar']; + $dataSet = [$field => []]; + $entity = $this->table->newEntity($dataSet); + static::assertArrayHasKey('scalar', $entity->getError($field)); + + $this->testDataValidationContains($this->table, $field, $dataSet, $expectedErrors); + } + /** * Test the testDataValidationNotContains base method. * From ef99a91be07315426db90d417b846bee4aabea40 Mon Sep 17 00:00:00 2001 From: Neluxx Date: Thu, 27 Aug 2026 14:51:18 +0200 Subject: [PATCH 14/15] Adjust unit tests --- tests/TestCase/Traits/DataValidationTestTraitTest.php | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/tests/TestCase/Traits/DataValidationTestTraitTest.php b/tests/TestCase/Traits/DataValidationTestTraitTest.php index 6b8c7fd..fc30589 100644 --- a/tests/TestCase/Traits/DataValidationTestTraitTest.php +++ b/tests/TestCase/Traits/DataValidationTestTraitTest.php @@ -280,9 +280,8 @@ public function testTestDataValidationScalar(): void { // Ensure data validation of the field works as expected first $field = 'scalar_field'; - $expectedErrors = ['scalar' => 'The provided value must be scalar']; - $dataSet = [$field => []]; - $this->testDataValidationContains($this->table, $field, $dataSet, $expectedErrors); + $entity = $this->table->newEntity([$field => []]); + static::assertArrayHasKey('scalar', $entity->getError($field)); $this->testDataValidationScalar($this->table, $field); } @@ -456,7 +455,7 @@ public function testTestDataValidationNaturalNumber(): void */ public function testTestFullDataValidationNoErrors(): void { - $dataSet = ['required_field' => 'required']; + $dataSet = ['required_field' => 'required', 'multi_rule_field' => 1]; $this->testFullDataValidationNoErrors($this->table, $dataSet); } @@ -468,7 +467,7 @@ public function testTestFullDataValidationNoErrors(): void */ public function testTestFullDataValidation(): void { - $dataSet = ['not_empty_field' => '']; + $dataSet = ['not_empty_field' => '', 'multi_rule_field' => 1]; $expectedErrors = [ 'not_empty_field' => ['_empty' => 'This field cannot be left empty'], 'required_field' => ['_required' => 'This field is required'], @@ -620,7 +619,7 @@ public function testTestDataValidationIsUniqueCustomExpected(): void public function testTestRules(): void { $field = 'unique_field'; - $dataSet = ['required_field' => 'required', $field => 'duplicate']; + $dataSet = ['required_field' => 'required', $field => 'duplicate', 'multi_rule_field' => 1]; $expectedErrors = ['_isUnique' => 'This value is already in use']; // Ensure a first record exists so the unique rule will fail on the second From 182cca46464d5cb84fb60014105910e47aafcde2 Mon Sep 17 00:00:00 2001 From: Neluxx Date: Thu, 27 Aug 2026 14:55:26 +0200 Subject: [PATCH 15/15] Add dedicated test for assertDataValidationErrorsContain helper --- .../Traits/DataValidationTestTraitTest.php | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/tests/TestCase/Traits/DataValidationTestTraitTest.php b/tests/TestCase/Traits/DataValidationTestTraitTest.php index fc30589..8643a39 100644 --- a/tests/TestCase/Traits/DataValidationTestTraitTest.php +++ b/tests/TestCase/Traits/DataValidationTestTraitTest.php @@ -674,7 +674,6 @@ public function testTestDataValidationNotContains(): void * * @return void * @covers ::testDataValidationInListContains - * @covers ::assertDataValidationErrorsContain */ public function testTestDataValidationInListContains(): void { @@ -699,6 +698,25 @@ public function testTestDataValidationInListNotContains(): void $this->testDataValidationInListNotContains($this->table, $validValues, $field, ['boolean']); } + /** + * Test the assertDataValidationErrorsContain base method. + * + * @return void + * @covers ::assertDataValidationErrorsContain + */ + public function testAssertDataValidationErrorsContain(): void + { + $field = 'boolean_field'; + // The errors contain the expected rule alongside an unrelated one, which must be ignored + $errors = [ + 'boolean' => 'The provided value must be a boolean', + 'maxLength' => 'The provided value is too long', + ]; + $expected = ['boolean' => 'The provided value must be a boolean']; + + $this->assertDataValidationErrorsContain($field, $errors, $expected); + } + /** * Test that a type-specific method only asserts its own rule and ignores unrelated errors. *