Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to unit test prepareForValidation on laravel Form Request?

Background: In my Laravel project, I am implementing the user creation function where an admin can create users. During user creation I'm setting a random password (not an input) and sending a password reset email to user. On my schema password field is not nullable (I don't want any users without passwords in the database).

I'm using Form Request to implement the controller validation and prepareForValidation to set the random password on POST but sets the password to null on PATCH as I don't want the password to be updated on user update event. (Admins can update user profiles but not the passwords.) And validation rules 'sometimes' handles the two scenarios.

Below is from my Request class (rules and prepareForValidation methods).

class AdminUserRequest extends FormRequest
{
    public function rules()
    {
        return [
            ...
            'password' => [
                'sometimes',
                'required',
                'min:8',
            ],
            ...
        ];
    }

    /**
     * Prepare the data for validation.
     *
     * @return void
     */
    protected function prepareForValidation()
    {
        if ($this->getMethod() == 'POST') {

            $this->merge([
                'password' => Hash::make(Str::random(10)),
            ]);
        } else {
            $this->offsetUnset('password');
        }
    }
}

Question: My question is what's the best way to unit test functionality of prepareForValidation in Form Request.

It should test that the request data set has a password on POST and and not set on other methods.

Of course I can do functional testing to cover the cases

  • POST to route creates a user in database and has no errors in session
  • PATCH to route doesn't change the password even if it's passed in the dataset.

But I'd like to know if there's a way to unit test this. And how to do it.

Thanks in advance

like image 765
Jude F'do Avatar asked Oct 20 '25 09:10

Jude F'do


1 Answers

It could be something like this (I didn't mock the request and arrange expectations on it - instead kept it simple). The last method is for private/protected method invocation of the classes. This can be moved to TestCase class for future usages.

class AdminUserRequestTest extends TestCase
{
    /**
     * @test
     * @covers ::prepareForValidation
     */
    function it_should_add_password_when_it_is_post_method()
    {
        $request = new AdminUserRequest();
        $request->setMethod('POST');
        $hashedPassword = 'a1b2c3d4e5';

        Hash::shouldReceive('make')->andReturn($hashedPassword);
        $this->invokeMethod($request, 'prepareForValidation');

        $this->assertSame($request->get('password'), $hashedPassword);
    }

    /**
     * @test
     * @covers ::prepareForValidation
     */
    function it_should_unset_password_when_it_is_not_post_method()
    {
        $request = new AdminUserRequest();
        $request->setMethod('PUT'); // it could be something else besides POST
        $this->invokeMethod($request, 'prepareForValidation');

        $this->assertFalse($request->has('password'));
    }

    protected function invokeMethod(&$object, $methodName, array $parameters = [])
    {
        $reflection = new ReflectionClass(get_class($object));
        $method = $reflection->getMethod($methodName);
        $method->setAccessible(true);

        return $method->invokeArgs($object, $parameters);
    }
}
like image 149
Ersoy Avatar answered Oct 23 '25 00:10

Ersoy



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!