Unit Test Laravel's Formrequest

Unit Test Laravel's FormRequest

I found a good solution on Laracast and added some customization to the mix.

The Code

/**
* Test first_name validation rules
*
* @return void
*/
public function test_valid_first_name()
{
$this->assertTrue($this->validateField('first_name', 'jon'));
$this->assertTrue($this->validateField('first_name', 'jo'));
$this->assertFalse($this->validateField('first_name', 'j'));
$this->assertFalse($this->validateField('first_name', ''));
$this->assertFalse($this->validateField('first_name', '1'));
$this->assertFalse($this->validateField('first_name', 'jon1'));
}

/**
* Check a field and value against validation rule
*
* @param string $field
* @param mixed $value
* @return bool
*/
protected function validateField(string $field, $value): bool
{
return $this->validator->make(
[$field => $value],
[$field => $this->rules[$field]]
)->passes();
}

/**
* Set up operations
*
* @return void
*/
public function setUp(): void
{
parent::setUp();

$this->rules = (new UserStoreRequest())->rules();
$this->validator = $this->app['validator'];
}

Update

There is an e2e approach to the same problem. You can POST the data to be checked to the route in question and then see if the response contains session errors.

$response = $this->json('POST', 
'/route_in_question',
['first_name' => 'S']
);
$response->assertSessionHasErrors(['first_name']);

How to unit test prepareForValidation on laravel Form Request?

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);
}
}

How to test form request rules in Laravel 5?

You need to have your form request class in the controller function, for example

public function store(MyRequest $request)

Now create HTML form and try to fill it with different values. If validation fails then you will get messages in session, if it succeeds then you get into the controller function.

When Unit testing then call the url and add the values for testing as array. Laravel doc says it can be done as

$response = $this->call($method, $uri, $parameters, $cookies, $files, $server, $content);

How to bind a model to a request in unit tests

The Route Model Binding is handled via a middleware, the SubstituteBindings middleware. So the request would have to pass through the middleware stack. Since you are not doing that I suppose you could set the parameter on the route yourself:

$route->setParameter($name, $value);

$route would be the route object returned from match.

Also when dealing with a Request, if you want a route parameter you should be explicit about it and not use the dynamic property as it will return an input before it falls back to returning a route parameter:

$this->route('timeSlot');

Skip Laravel's FormRequest Validation

With the information provided I'd say you are executing an integration test which does an actual web request. In such a context I'd say it's fine for your test suite to connect to a 3rd party since that's part of 'integrating'.

In case you still prefer to mock the validation rule you could swap out the Validator using either the swap

$mock = Mockery::mock(Validator::class);
$mock->shouldReceive('some-method')->andReturn('some-result');
Validator::swap($mock);

Or by replacing its instance in the service container

$mock = Mockery::mock(Validator::class);
$mock->shouldReceive('some-method')->andReturn('some-result');
App:bind($mock);

Alternatively you could mock the Cache::remember() call which is an interal part of the Pwned validation rule itself. Which would result into something like

Cache::shouldReceive('remember')
->once()
->andReturn(new \Illuminate\Support\Collection([]));

Laravel custom validation rule mock in a test case

You should make valid requests to your endpoints, otherwise your are not testing anything,
what if your controller actually needs a user_id ? You will get a 500 error down the line.

Better to use factories to create a test database that has everything you need, and transmit data that is valid and in your test database

laravel unit testing error 'call to member function call() on null'

It is really easy, you just don't mock a FormRequest. You have to do a Feature test instead, using $this->get or $this->post or whatever action you need.

So you just pass the right parameters to your route so FormRequest literally passes the validation. This is the only way of testing this.

If you explain why you want to do it this way, I could expand my answer, because I have no idea why you want to do it this way...

Mocking Laravel custom validation rule class not working

The class must be instantiated through Laravels service container in order for it to be mocked. The best way to accomplish this (in this situation) is to simply change new Captcha to app(Captcha::class):

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;
use App\Rules\Captcha;

class NewUserRequest extends FormRequest {

public function rules()
{
return [
'name' => ['required', app(Captcha::class)]
];
}
}

I would advise against telling the rule itself to change its behaviors based on environments as that could result in a bit of confusion down the line when you are trying to figure out why the captcha doesn't do anything in dev environments but is failing in production.



Related Topics



Leave a reply



Submit