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
Getting the Name of a Child Class in the Parent Class (Static Context)
Correct Indentation of HTML and PHP Using Vim
Why, Fatal Error: Class 'Phpunit_Framework_Testcase' Not Found in ...
How to Execute Raw Queries with Laravel 5.1
Issue in Installing PHP7.2-Mcrypt
Create New Xml File and Write Data to It
How to Render Zf2 View Within JSON Response
How to Paginate a Merged Collection in Laravel 5
How to Alias the Name of a Column in Eloquent
How to Add Private Github Repository as Composer Dependency
Codeigniter Check for User Session in Every Controller
Get Div Content from External Website
Calculate Total Seconds in PHP Dateinterval
How to Echo the Whole Content of a .HTML File in PHP
Executing a Powershell Script from PHP
How to Avoid Code Repetition with PHP SQL Prepared Statements