Better way for testing validation errors - validation

I'm testing a form where user must introduce some text between let's say 100 and 500 characters.
I use to emulate the user input:
$this->actingAs($user)
->visit('myweb/create')
->type($this->faker->text(1000),'description')
->press('Save')
->see('greater than');
Here I'm looking for the greater than piece of text in the response... It depends on the translation specified for that validation error.
How could do the same test without having to depend on the text of the validation error and do it depending only on the error itself?
Controller:
public function store(Request $request)
{
$success = doStuff($request);
if ($success){
Flash::success('Created');
} else {
Flash::error('Fail');
}
return Redirect::back():
}
dd(Session::all()):
`array:3 [
"_token" => "ONoTlU2w7Ii2Npbr27dH5WSXolw6qpQncavQn72e"
"_sf2_meta" => array:3 [
"u" => 1453141086
"c" => 1453141086
"l" => "0"
]
"flash" => array:2 [
"old" => []
"new" => []
]
]

you can do it like so -
$this->assertSessionHas('flash_notification.level', 'danger'); if you are looking for a particular error or success key.
or use
$this->assertSessionHasErrors();

I think there is more clear way to get an exact error message from session.
/** #var ViewErrorBag $errors */
$errors = request()->session()->get('errors');
/** #var array $messages */
$messages = $errors->getBag('default')->getMessages();
$emailErrorMessage = array_shift($messages['email']);
$this->assertEquals('Already in use', $emailErrorMessage);
Pre-requirements: code was tested on Laravel Framework 5.5.14

get the MessageBag object from from session erros and get all the validation error names using $errors->get('name')
$errors = session('errors');
$this->assertSessionHasErrors();
$this->assertEquals($errors->get('name')[0],"The title field is required.");
This works for Laravel 5 +

Your test doesn't have a post call. Here is an example using Jeffery Way's flash package
Controller:
public function store(Request $request, Post $post)
{
$post->fill($request->all());
$post->user_id = $request->user()->id;
$created = false;
try {
$created = $post->save();
} catch (ValidationException $e) {
flash()->error($e->getErrors()->all());
}
if ($created) {
flash()->success('New post has been created.');
}
return back();
}
Test:
public function testStoreSuccess()
{
$data = [
'title' => 'A dog is fit',
'status' => 'active',
'excerpt' => 'Farm dog',
'content' => 'blah blah blah',
];
$this->call('POST', 'post', $data);
$this->assertTrue(Post::where($data)->exists());
$this->assertResponseStatus(302);
$this->assertSessionHas('flash_notification.level', 'success');
$this->assertSessionHas('flash_notification.message', 'New post has been created.');
}

try to split your tests into units, say if you testing a controller function
you may catch valication exception, like so:
} catch (ValidationException $ex) {
if it was generated manually, this is how it should be generated:
throw ValidationException::withMessages([
'abc' => ['my message'],
])->status(400);
you can assert it liks so
$this->assertSame('my message', $ex->errors()['abc'][0]);
if you cannot catch it, but prefer testing routs like so:
$response = $this->json('POST', route('user-post'), [
'name' => $faker->name,
'email' => $faker->email,
]);
then you use $response to assert that the validation has happened, like so
$this->assertSame($response->errors->{'name'}[0], 'The name field is required.');
PS
in the example I used
$faker = \Faker\Factory::create();
ValidationException is used liks this
use Illuminate\Validation\ValidationException;
just remind you that you don't have to generate exceptions manually, use validate method for common cases:
$request->validate(['name' => [
'required',
],
]);
my current laravel version is 5.7

Related

How to remove console error 422 (Unprocessable Content)

I write validation for email in my controller
public function store(Request $request)
{
/*
* validate request
*/
$request->validate([
'email' => ['required', 'unique:leads', 'email'],
]);
return response()->json([],422);
if (\App\Models\Lead::where(['email' => $request->get('email')])->count() > 0) {
// user found
return response()->json([ 'data' => [
'message' => 'lindirizzo email è già registrato'
]], 200);
}
else {
// Register the new user or whatever.
$client = \App\Models\Lead::create(['email' => $request->get('email'),]);
return response()->json([ 'data' => [
'message' => 'Registrato con successo!'
]], 201);
}
}
And when i write the same email in front i get error POST http://127.0.0.1:8000/api/register/leads 422 (Unprocessable Content)
My network respons its ok,
{"message":"validation.unique","errors":{"email":["validation.unique"]}}
But i dont want to show error in console
This line of code is unnecessary.
// ...
return response()->json([],422);
// ...
Remove it.
Writing The Validation Logic
... if the validation fails, the proper response will automatically be
generated. If the validation passes, our controller will continue
executing normally.
Addendum
The block of code below is unnecessary as well since the validation rule 'unique:leads' is sufficient:
// ...
if (\App\Models\Lead::where(['email' => $request->get('email')])->count() > 0) {
// user found
return response()->json([ 'data' => [
'message' => 'lindirizzo email è già registrato'
]], 200);
}
// ...
Remove it as well!
If you wish to have a custom message for the "unique email" validation, add it as the second parameter of the ->validate(...) method:
$request->validate([
'email' => ['required', 'unique:leads', 'email'],
], [
'email.unique' => 'lindirizzo email è già registrato'
]);
First, you need to check is there any email is available already or not in the table.
If you found any count of that email then simply ignore creating a record else you can add that email with a new entry inside the database.
if (Lead::where(['email' => $request->get('email')])->count() > 0) {
return response()->json([ 'data' => [
'message' => 'User data get successfully'
]], 200);
}else{
// Add a user with new email and send json response
}
}

How do I run a phpunit test on Redis pub/sub?

I'm building a messenger system with Redis publishing on the Laravel end and subscribing on a node server. I would like to test what is stored in the redis pub method using PHPUnit, but I have no idea where to start.
Controller
class MessageController extends Controller
{
public function store(Conversation $conversation, Request $request)
{
$user = Auth::user();
$message = Message::create([
'body' => $request->input('message'),
'conversation_id' => $conversation->id,
'sender_id' => $user->id,
'type' => 'user_message'
]);
$redis = Redis::connection();
$data = new MessageResource($message);
$redis->publish('message', $data);
}
}
Current Test
/** #test */
public function a_user_can_send_a_message()
{
$this->actingAs($user = User::factory()->create());
$message = Message::factory()->make(['sender_id' => $user->id]);
$conversation Conversation::factory()->create();
$response = $this->json('POST', '/api/message/'. $conversation->id, ['message' => $message->body])
->assertStatus(201);
$response->assertJsonStructure([
'data' => [
'body',
'sender',
]
]);
}
Essentially what I'm trying to see is if message has been published on Redis. I'm unsure how to do this, and I think you would probably need to clear the message from Redis after, would you not?
Your test should be like this:
public function test_a_user_can_send_a_message()
{
$redisSpy = Redis::spy();
$redisSpy->shouldReceive('connection')->andReturnSelf();
$this->actingAs($user = User::factory()->create());
$message = Message::factory()->make(['sender_id' => $user->id]);
$conversation = Conversation::factory()->create();
$this->postJson("/api/message/{$conversation->id}", ['message' => $message->body]);
$this->assertDatabaseCount('messages', 1);
$redisSpy->shouldHaveReceived('publish')
->with('message', new MessageResource(Message::first()));
}
As you can see, I have added Redis::spy(); this is going to allow is to "spy" what is called from Redis. You can still mock methods, and we have to do so, because you use Redis::connect(); and then $redis->publish(...), so we will return the spy when connect is called, that is why we do shouldReceive('connection')->andReturnSelf().
At the end of the code, we check that $redis->publish was called with parameters 'message' and a resource with the desired message. Both must match for this assertion to pass, else you will see a mock error.

Problems with Laravel data validation

I'm having some problems with validations in my api.
I need to send a json array like this:
[
{
"acktime": "2021-09-25 08:45:07",
"temp": 15.6
},
{
"acktime": "2021-09-25 08:45:07",
"temp": 15.6
}
probably more array....
]
I would like to vaidate one by one array and store only the valid data returning error for unvalid data, I have tried a foreach cylce but it convert the array to object but the validate::make want only array.
I have tried this:
$validator = Validator::make($request->all(), [
'*.acktime' => 'required',
'*.temp' => 'required|numeric'
]);
$validatedData = $validator->validated();
var_dump($validatedData);
return response()->json($validatedData);
But If I send wrong data I get only error without having valid data, so I've tried this way:
foreach($datas as $data){
$arr = (array)$data;
$validator = Validator::make($arr, [
'acktime' => 'required',
'temp' => 'required|numeric'
]);
if ($validator->fails()) {
continue;
} else {
$newrawData = new rawData([
'acktime' => $data->acktime,
'temp' => $data->temp,
'synctime' => now()
]);
$newrawData->save();
}
}
return response('OK', 200); //or error if some data are not ok
}
In this way it work, bot I have no idea about get, a probable, validation error..(for the moment there's a continue for continue the cycle) any suggestion?
There are two ways for approaching this kind of validation:
make a custom rule in laravel validation from below and put your validation code in it and this will work:
https://laravel.com/docs/8.x/validation#custom-validation-rules
easier way:
$data = [ 'data' => $requests->all() ];
$validator = Validator::make($data, [
'data.*.name' => 'required|string',
'data.*.' => 'required|string'
]);

Find data before validate form request laravel

I want to update the data using the request form validation with a unique email role, everything works normally.
Assume I have 3 data from id 1-3 with url:
127.0.0.1:8000/api/user/update/3
Controller:
use App\Http\Requests\Simak\User\Update;
...
public function update(Update $request, $id)
{
try {
// UPDATE DATA
return resp(200, trans('general.message.200'), true);
} catch (\Exception $e) {
// Ambil error
return $e;
}
}
FormRequest "Update":
public function rules()
{
return [
'user_akses_id' => 'required|numeric',
'nama' => 'required|max:50',
'email' => 'required|email|unique:users,email,' . $this->id,
'password' => 'required',
'foto' => 'nullable|image|max:1024|mimes:jpg,png,jpeg',
'ip' => 'nullable|ip',
'status' => 'required|boolean'
];
}
but if the updated id is not found eg:
127.0.0.1:8000/api/user/update/4
The response gets The email has already been taken.
What is the solution so that the return of the data is not found instead of validation first?
The code looks like it should work fine, sharing a few things below that may help.
Solution 1: Check if $this->id contains the id you are updating for.
Solution 2: Try using the following changes, try to get the id from the URL segment.
public function rules()
{
return [
'user_akses_id' => 'required|numeric',
'nama' => 'required|max:50',
'email' => 'required|email|unique:users,email,' . $this->segment(4),
'password' => 'required',
'foto' => 'nullable|image|max:1024|mimes:jpg,png,jpeg',
'ip' => 'nullable|ip',
'status' => 'required|boolean'
];
}
Sharing one more thing that may help you.
Some person uses Request keyword at the end of the request name. The Update sounds generic and the same as the method name you are using the request for. You can use UpdateRequest for more code readability.
What I understand from your question is, you need a way to check if the record really exists or not in the form request. If that's the case create a custom rule that will check if the record exists or not and use that rule inside your request.
CheckRecordRule
namespace App\Rules;
use Illuminate\Contracts\Validation\Rule;
class CheckRecordRule implements Rule
{
protected $recordId;
public function __construct($id)
{
$this->recordId = $id;
}
public function passes($attribute, $value)
{
// this will check and return true/false
return User::where('id', $this->recordId)->exists();
}
public function message()
{
return 'Record not found.';
}
}
Update form request
public function rules()
{
return [
'email' => 'required|email|unique:users,email,' . $this->id.'|'. new CheckRecordRule($this->id),
];
}
So when checking for duplicate it will also check if the record really exists or not and then redirect back with the proper message.

Laravel Test on controller returning uncorrect status code

I'm trying to create a test on a rest API using Laravel. My test is in 3 steps : Arrange, act and assert.
During the first step I create an entry in the database. For the second test i query my DB, finally i check if the tests are true or not.
use RefreshDatabase;
/** #test */
public function createTest() {
// Arrange : name & message OK
$message = ChatMessages::create([
'username' => 'My name is long enough',
'message' => 'my message is also OK'
]);
// Act
$getAllMessages = ChatMessages::all();
$this->post('/message', $message->toArray())
->assertStatus(200)
->assertExactJson([
'status' => 'ok', 'chat_message' => $message->toArray()
]);
// assert : if name too short, data is not persisted
$this->assertEquals(sizeof($getAllMessages), 1);
}
My route :
Route::post('/message', 'ChatController#create');
I have the following error message :
Expected status code 200 but received 404.
Failed asserting that false is true.
In my Controller I have this code :
public function create(Request $request) {
$input = $request->validate([
'username' => 'required|string|min:3|max:255',
'message' => 'required|string|min:1',
]);
try {
$message = ChatMessages::create([
'username' => $input['username'],
'message' => $input['message'],
]);
} catch (\Exception $err) {
Log::error("Cannot fetch chat messages");
Log::error($err);
abort(500);
}
return response()->json(['status' => 'ok', 'chat_message' => $message]);
}
$request->validate should give a 302 redirection if not satisfied and 200 if OK.

Resources