====== Table Tests ====== ===== Data Validation Tests ===== Data Validation Tests in our application are done with the DataValidationTestTrait. This trait in most cases first checks the given fields validation with a positive case, where it should run throught without problem and then with a negative case where it should cause an expected validation error. This usually leads to quite a few lines of code like this: public function testValidationDefault(): void { $accounts = $this->Accounts; $field = 'uuid'; $this->testDataValidationScalar($accounts, $field); $this->testDataValidationRequired($accounts, $field); $this->testDataValidationNotEmpty($accounts, $field); $this->testDataValidationUuid($accounts, $field); $field = 'name'; $this->testDataValidationScalar($accounts, $field); $this->testDataValidationMaxLength($accounts, $field, 120); $this->testDataValidationRequired($accounts, $field); $this->testDataValidationNotEmpty($accounts, $field); $field = 'email'; $this->testDataValidationEmail($accounts, $field); $this->testDataValidationRequired($accounts, $field); $this->testDataValidationNotEmpty($accounts, $field); $field = 'company'; $this->testDataValidationScalar($accounts, $field); $this->testDataValidationMaxLength($accounts, $field, 120); $this->testDataValidationRequired($accounts, $field); $this->testDataValidationNotEmpty($accounts, $field); $field = 'currency'; $this->testDataValidationScalar($accounts, $field); $this->testDataValidationMaxLength($accounts, $field, 3); $this->testDataValidationRequired($accounts, $field); $this->testDataValidationNotEmpty($accounts, $field); } ==== Problem with DataValidationTestTrait ==== A recent problem I had with the DataValidationTestTrait was the "testDataValidationIsUnique" method. The problems with it were: - It was badly documented. I was not aware that the "fieldValue" was not supposed to be an already existing value, since that would make sense to test. It creates its own new entry and then does that again, which should lead to an error because of a Unique field. - The validation had to be turned off. To test uniqueness you have to test the buildRules which trigger after the validation. - If there are fields that are "not null" in the database, you have to set them, else you'll get it rejected by the database table. - If the field you had to set is ALSO unique, the current logic of the "isUnique" method will use the same data to make both entries. So it will not only run into a "unique" error for the field you set and actually should run into it, but also the field you were forced to set, since it can't be null. This leads to 2 unique errors which breaks the test. I currently have 2 possible solutions for this in mind - Completely rewrite the logic of the trait to how I thought it initially worked. Make it so that you have to give it an already existing value and it only tries saving once. You can set the other fields to valid fields that ARE unique since you know what already is in your database, so theres only one unique error. Then just assert that it causes a unique error on that field. This isn't such a great idea however because it relies on you already having an entry in a fixture. - Keep the logic but implement it so that if the error you're looking for is contained within the list of errors it still runs through. I'm not sure how well this will work since it causes a different kind of error than validation errors. So it ended up being a much simpler mistake. We forgot to disable validation on BOTH the times it was saved. The existing logic actually allows there being multiple different errors since it searches for a specific one in the list of errors it gets. We corrected it now in the trait itsself and it works properly now. The issue that you have to give it all the required fields still exists tho since it is not a data validation error. So an implementation would now look like this: $this->testDataValidationIsUnique( $subscriptions, 'uuid', '12345681-4ad6-43e7-842c-aeb392a0f862', [ 'account_id' => '1', 'resource' => Resource::MEM->value, 'amount' => 17179869184, 'price' => 150.50, 'status' => SubscriptionStatuses::ACTIVE->value, 'auto_renew' => 1, 'free_tier' => 0, 'start_time' => '2025-11-07 15:46:40', 'end_time' => '2026-11-07 15:46:40', 'period' => '365 days, 1:28:20.246798', ] ); And the trait now looks like this: protected function testDataValidationIsUnique( Table $table, string $fieldName, mixed $fieldValue, array $additionalProperties = [], ?array $expected = null, ): void { $prevEntity = $table->newEmptyEntity(); $table->patchEntity( $prevEntity, array_merge($additionalProperties, [$fieldName => $fieldValue]), ['validate' => false], // <--- We noticed this before ); $table->saveOrFail($prevEntity); $entity = $table->newEmptyEntity(); $table->patchEntity( $entity, array_merge($additionalProperties, [$fieldName => $fieldValue]), ['validate' => false], // <--- BUT WE MISSED THIS ); $result = $table->checkRules($entity); static::assertFalse($result); $expected ??= ['_isUnique' => 'This value is already in use']; $this->assertDataValidationErrorsContain($fieldName, $entity->getError($fieldName), $expected); }