dwightwatson / validating

Automatically validating Eloquent models for Laravel
MIT License
968 stars 76 forks source link

ValidationException: Change $message when an ValidationException occurs during Tests? #226

Closed JayDijkstra closed 3 years ago

JayDijkstra commented 3 years ago

Problem in short:

Within most of my test methods, I am generating test-data via factory class ( Laravel 6 and lower). There are those moments that a test fails, because of a model containing 'invalid' data (validated by the Watson validator).

This failures mostly occur due to an model being validated as invalid. (e.g. a duplicate entry for a unique rule). No matter on what rule the watson validator return invalid, and fails the test.. the default message will always be:

Functional Tests (1) --------------------------------------------------------------------------------------------------------
Modules: Asserts, Db, Cli, Filesystem, FunctionalHelper, Laravel5
-----------------------------------------------------------------------------------------------------------------------------
E UserFactoryTest: User factory (0.17s)
-----------------------------------------------------------------------------------------------------------------------------

Time: 233 ms, Memory: 42.50 MB

There was 1 error:

---------
1) UserFactoryTest: User factory
 Test  tests/Codeception/Functional/FactoryTest.php:testUserFactory
  [Watson\Validating\ValidationException] The given data was invalid.  

Differences Application and Testsuite?

I did try to extract extra debug information by logging the errors via the Handler.php class:

    /**
     * Report or log an exception.
     *
     * This is a great spot to send exceptions to Sentry, Bugsnag, etc.
     *
     * @param \Exception $exception
     *
     * @throws Exception
     */
    public function report(Exception $exception)
    {
        if ($exception instanceof \Watson\Validating\ValidationException) {
            // Just a test-debug for logging the first error message.
            logger()->error($exception->model()->getErrors()->first());
        }

        // Report the exception.
        parent::report($exception);
    }

This is not a real effective solution because I have to scroll between multiple lines of stacktraces before actually finding the validation errors (needle / haystack principle). Also this only works when a ValidationException is thrown within the application (on a Controller's store or update method). The TestSuite does not trigger the Handler;s report() or render() method, not even with $this->withExceptionHandling() defined in a testmethod.

I did also try to add two array's to the model ( I am making use of an Abstract method which has the ValidatingTrait implemented on it). On this way the children of the abstract class will automatically be triggering the Watson validation Events.

AbstractModel.php

/**
  * Array to hold error messages.
  * 
  * @var string[]
 */
protected $validationMessages = [];

/**
  * Array to hold error messages.
  * 
  * @var string[]
 */
protected $validationErrors = [];

User.php


/**
  * Array to hold error messages.
  * 
  * @var string[]
 */
protected $validationMessages = 
    [
        'username' => 'Username must be unique per user.',
    ];

/**
  * Array to hold error messages.
  * 
  * @var string[]
*/
protected $validationErrors = 
    [
        'username' => 'Username must be unique per user.',
    ];

These arrays will will not change the default message The given data was invalid., which still leaves me with questions.

It would really be great if there is a method, or implementation on which I can change Laravel's default error message with the error bag (or a combined string of all errors within the error bag) as how the QueryException works when trying to insert data in a test:

QueryException example:

lluminate\Database\QueryException : SQLSTATE[22007]: 1366 Incorrect integer value: 'non' for column `database`.`users`.`id` at row 1 (SQL: insert into `users` (`name`, `email`, `password`, `type`, `id`,  `updated_at`, `created_at`) values (Giel Farah, tcicek@example.org, , 0, 0, 2020-12-10 14:09:05, 2020-12-10 14:09:05))

I hope someone could help me out!!

Kind Regards.

dwightwatson commented 3 years ago

The generic message you're seeing The given data was invalid is just the default exception message of Laravel's ValidationException and it doesn't reflect the actual error that triggered it. You can get the error messages off of the exception with $exception->errors() which would give you the array of human-readable error messages you're after.

As far as the error in your tests goes, you've likely either got a faulty factory (that is re-using unique keys) or you're not cleaning up the database properly between tests which is leading to duplicate keys.

Hope this helps!

JayDijkstra commented 3 years ago

Hey dwight,

The $exception->errors() is the extra information which I needed!

Thank you!!!