abkrim's avatar
Level 13

Fail test featured with not fail in test unit

I've a class rule:

<?php

namespace App\Rules;

use Closure;
use Illuminate\Contracts\Validation\ValidationRule;

class DecimalRule implements ValidationRule
{
    protected int $integerLength;
    protected int $decimalLength;

    public function __construct(int $integerLength, int $decimalLength)
    {
        $this->integerLength = $integerLength;
        $this->decimalLength = $decimalLength;
    }

    /**
     * Run the validation rule.
     *
     * @param  \Closure(string): \Illuminate\Translation\PotentiallyTranslatedString  $fail
     */
    public function validate(string $attribute, mixed $value, Closure $fail): void
    {
        ray($attribute, $value);
        // Check if the value is a number
        if (!is_numeric($value)) {
            $fail("The {$attribute} must be a numeric value.");
            return;
        }

        // Check the length of the integer part
        $parts = explode('.', $value);

        $integerPart = $parts[0];
        $decimalPart = $parts[1] ?? '';

        // Check the length of the integer part
        if (strlen($integerPart) > $this->integerLength) {
            $fail("The {$attribute} must have no more than {$this->integerLength} integer digits.");
            return;
        }

        // Check the length of the decimal part
        if (strlen($decimalPart) > $this->decimalLength) {
            $fail("The {$attribute} must have no more than {$this->decimalLength} decimal digits.");
        }
    }
}

Unit testing

<?php

namespace Tests\Unit\Rules;

use App\Rules\DecimalRule;
use Tests\TestCase;

class DecimalRuleTest extends TestCase
{
    /** @test */
    function it_passes_with_valid_data()
    {
        $rule = new DecimalRule(9, 6);
        $validator = $this->app['validator']->make(
            ['testAttribute' => '123456789.123456'],
            ['testAttribute' => $rule]
        );

        $this->assertFalse($validator->fails());
    }

    /** @test */
    function it_fails_with_invalid_integer_length()
    {
        $rule = new DecimalRule(9, 6);
        $validator = $this->app['validator']->make(
            ['testAttribute' => '1234567890.123456'],
            ['testAttribute' => $rule]
        );

        $this->assertTrue($validator->fails());
    }

    /** @test */
    function it_fails_with_invalid_decimal_length()
    {
        $rule = new DecimalRule(9, 6);
        $validator = $this->app['validator']->make(
            ['testAttribute' => '123456789.1234567'],
            ['testAttribute' => $rule]
        );

        $this->assertTrue($validator->fails());
    }

    /** @test */
    function it_fails_if_number_is_greater()
    {
        $rule = new DecimalRule(9, 6);
        $validator = $this->app['validator']->make(
            ['testAttribute' => '10000000000'],
            ['testAttribute' => $rule]
        );

        $this->assertTrue($validator->fails());
    }
}

Work fine.

Now I have a Request class

<?php

namespace App\Http\Requests;

use App\Rules\DecimalRule;
use Bouncer;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Auth;
use Illuminate\Validation\Rule;

class CommandCenterHistoricalFactRequest extends FormRequest
{
    public function authorize(): bool
    {
        return Auth::user()->isSuperAdmin() || Bouncer::is(Auth::user())->a('administrator');

    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array|string>
     */
    public function rules(): array
    {
        return [
            'globalSearch' => 'filled',
            'download' => [
                'filled',
                'prohibits:perPage',
                Rule::in(['xlsx', 'csv', 'ods', 'html', 'pdf']),
            ],
            'filter.id' => 'integer|filled',
            'filter.command_center_id' => 'int|filled|exists:command_centers,id',
            'filter.eneact_kwh' => ['filled', new DecimalRule(9, 6)],
            'filter.is_historical' => 'boolean|filled',
            'filter.date' => 'filled|date_format:Y-m-d',

            'page' => 'filled|int',
            'perPage' => [
                'int',
                'filled',
                'required_with:pag',
                Rule::in([5, 10, 25, 50, 100, 500]),
            ],
        ];
    }
}

And testing

<?php

namespace Tests\Feature\Http\Requests;

use App\Http\Requests\CommandCenterHistoricalFactRequest;
use Illuminate\Support\Facades\Validator;
use Tests\TestCase;

class CommandCenterHistoricalFactRequestTest extends TestCase
{
    /** @test */
    public function it_passes_with_valid_data_at_cc_historical_fact_request()
    {
        $validator = Validator::make([
            'globalSearch' => 'example',
            'download' => 'csv',
            'filter.id' => 1,
            'filter.command_center_id' => 1,
            'filter.eneact_kwh' => 123.45,
            'filter.is_historical' => true,
            'filter.date' => '2023-05-09',
            'page' => 1
        ], (new CommandCenterHistoricalFactRequest())->rules());

        $this->assertFalse($validator->fails());
    }

    /** @test */
    function it_fails_with_invalid_eneact_kwh_at_cc_historical_fact_request()
    {
        $validator = Validator::make([
            'globalSearch' => 'example',
            'download' => 'csv',
            'filter.id' => 1,
            'filter.command_center_id' => 1,
            'filter.eneact_kwh' => 123.1234567,
            'filter.is_historical' => true,
            'filter.date' => '2023-05-09',
            'page' => 1
        ], (new CommandCenterHistoricalFactRequest())->rules());

        $this->assertTrue($validator->fails());
    }

    /** @test */
    public function it_fails_with_invalid_data_at_cc_historical_fact_request()
    {
        $validator = Validator::make([
            'globalSearch' => '',
            'download' => 'invalid-format',
            'filter.id' => 'string',
            'filter.command_center_id' => 'string',
            'filter.eneact_kwh' => 'invalid-number',
            'filter.is_historical' => 'invalid-boolean',
            'filter.date' => 'invalid-date',
            'page' => 'invalid-page',
            'perPage' => 999
        ], (new CommandCenterHistoricalFactRequest())->rules());

        $this->assertTrue($validator->fails());
    }


    /** @test */
    public function it_fails_with_invalid_eneact_kwh()
    {
        $validator = Validator::make([
            'filter.eneact_kwh' => '10000000000.000001'
        ], (new CommandCenterHistoricalFactRequest())->rules());

        ray($validator->messages());
        $this->assertTrue($validator->fails());
    }
}

Lates test it_fails_with_invalid_eneact_kwh fails.

Rule validate a bad value greater.

 at tests/Feature/Http/Requests/CommandCenterHistoricalFactRequestTest.php:72
     68▕             'filter.eneact_kwh' => 123.1234567,
     69▕         ], (new CommandCenterHistoricalFactRequest())->rules());
     70▕ 
     71▕         ray($validator->messages());
  ➜  72▕         $this->assertTrue($validator->fails());

Desesperate

0 likes
2 replies
LaryAI's avatar
Level 58

The issue seems to be with the DecimalRule class failing to validate a bad value greater than the allowed limit. One possible solution is to add an additional check in the validate method to ensure that the integer part of the value is not greater than the allowed limit. Here's an updated version of the DecimalRule class:

namespace App\Rules;

use Closure;
use Illuminate\Contracts\Validation\ValidationRule;

class DecimalRule implements ValidationRule
{
    protected int $integerLength;
    protected int $decimalLength;

    public function __construct(int $integerLength, int $decimalLength)
    {
        $this->integerLength = $integerLength;
        $this->decimalLength = $decimalLength;
    }

    /**
     * Run the validation rule.
     *
     * @param  \Closure(string): \Illuminate\Translation\PotentiallyTranslatedString  $fail
     */
    public function validate(string $attribute, mixed $value, Closure $fail): void
    {
        ray($attribute, $value);
        // Check if the value is a number
        if (!is_numeric($value)) {
            $fail("The {$attribute} must be a numeric value.");
            return;
        }

        // Check the length of the integer part
        $parts = explode('.', $value);

        $integerPart = $parts[0];
        $decimalPart = $parts[1] ?? '';

        // Check the length of the integer part
        if (strlen($integerPart) > $this->integerLength) {
            $fail("The {$attribute} must have no more than {$this->integerLength} integer digits.");
            return;
        }

        // Check the length of the decimal part
        if (strlen($decimalPart) > $this->decimalLength) {
            $fail("The {$attribute} must have no more than {$this->decimalLength} decimal digits.");
            return;
        }

        // Check if the value is greater than the allowed limit
        if ($integerPart > str_repeat('9', $this->integerLength)) {
            $fail("The {$attribute} must not be greater than " . str_repeat('9', $this->integerLength) . ".");
            return;
        }
    }
}

This updated version adds an additional check to ensure that the integer part of the value is not greater than the allowed limit. The check is done by comparing the integer part of the value with a string of nines of the same length as the allowed limit. If the integer part is greater than the string of nines, the validation fails.

With this update, the DecimalRuleTest should pass all tests, including the one that was failing previously.

abkrim's avatar
Level 13

Not work. Problem is test not passes by DecimalRule.

 public function validate(string $attribute, mixed $value, Closure $fail): void
    {
        ray($attribute, $value);
...

ray() not show anything.

Please or to participate in this conversation.