I'm new in unit tests in laravel and currently facing an error on my test. Please see my code below.
Test
/** #test */
public function users_can_view_homepage_products()
{
$response = $this->get('api/products');
$response->assertStatus(200)
->assertJson([
'id' => 1,
'name' => ucwords($this->faker->words(3, true)),
'slug' => ucfirst($this->faker->slug),
'intro' => $this->faker->sentence,
'price' => number_format($this->faker->randomFloat(2, 100, 99999), 2)
]);
}
Controller
public function index()
{
return [
'id' => 1,
'name' => 'Airpods Pro (2021)',
'slug' => 'airpods-pro-2021',
'intro' => 'New and powerful airpods from apple.',
'price' => 12400
];
}
Error
The test fails because the content of the json is different to that being tested. You probably want to test that the structure is the same, rather than the content:
/** #test */
public function users_can_view_homepage_products()
{
$response = $this->get('api/products');
$response->assertStatus(200)
->assertJsonStructure([
'id',
'name'
'slug'
'intro'
'price'
]);
}
Related
I'm developing an API with Laravel. In one of the endpoint I'm accessing, some fields are showing a null value, but it should have some information.
Note the "addicionais_descricao" and "valor" fields, both always come with null values when I include them in the attributeitems array, but if I leave it at the initial level, the data is presented, but it doesn't solve my case, because I need this information with the attribute items:
enter image description here
This is where the endpoint calls, I make the query in the "Attribute" table, which has a relationship with the "Attributeitems" table, while the "attributeitems" table is linked to "Attribute" and "product".
public function show($id)
{
$atributos = Atributo::query('atributo')
->select(
'atributo.id',
'atributo.atrdescricao',
'atributoitens.atributo_id',
'atributoitens.produto_id',
'produto.prodescricao',
'produto.provalor'
)
->leftJoin('atributoitens', 'atributo.id', '=', 'atributoitens.atributo_id')
->leftJoin('produto', 'produto.id', '=', 'atributoitens.produto_id')
->where('atributo.id', '=', $id)
->get()->unique('id');
return AtributoResource::collection($atributos);
}
Resource Atributo:
public function toArray($request)
{
return [
'id' => $this->id,
'descricao' => $this->atrdescricao,
'atributoitens' => AtributoitensResource::collection($this->atributoitens),
];
}
Resource Atributo Itens:
public function toArray($request)
{
return [
'id' => $this->id,
'atributo' => $this->atributo_id,
'produtos' => $this->produto_id,
'adicionais_descricao' => $this->prodescricao,
'valor' => $this->provalor
];
}
What is the correct procedure for this situation?
Take this example as a reference :
Controller
$data = $shop->products()
->whereStatus(true)
->where('product_shop.active', true)
->where('product_shop.quantity', '>=', $this->min_product_qty)
->paginate(50);
return (new ProductCollection($data))
->response()
->setStatusCode(200);
ProductCollection
public function toArray($request)
{
return [
'data' => $this->collection
->map(function($product) use ($request) {
return (new ProductResource($product))->toArray($request);
}),
'brand' => $this->when($request->brand, $request->brand)
];
}
ProductResource
public function toArray($request)
{
return [
'type' => 'product',
'id' => (string) $this->id,
'attributes' => [
'uuid' => $this->uuid,
'name' => $this->name,
'slug' => $this->slug,
'description' => $this->description,
'thumb_path' => $this->thumb_path,
'cover_path' => $this->cover_path,
],
'relationships' => [
'brand' => $this->brand
]
];
}
Something like this should help you do what you want. I cant exactly do it for you. by the way why you are not using Eloquent, something like
Attribute::where(...)->with(['relation_1', 'products'])->get();
public function toArray($request)
{
return [
'id' => $this->id,
'attributes' => [...],
'products' => $this->collection
->map(function($this->product) use ($request) {
return (new ProductResource($product))->toArray($request);
}),
];
}
The resource collection:
public function toArray($request)
{
return [
'test' => 55,
'id' => $this->id,
'name_en' => $this->name_en,
'name_ar' => $this->name_ar,
'slug' => $this->slug,
'details' => $this->details,
'currency' => $this->currency,
'offer' => $this->offer->first(),
'brand' => $this->brand,
'category' => $this->category,
'specifications' => SpesificationResource::collection($this->specifications),
'merchant' => $this->merchant,
'images' => count($this->images) > 0 ? ImageResource::collection($this->images) : asset("/images/default.png"),
"price" => $this->price,
"finalPrice" => $this->offer->first() ? $this->price - ($this->price * $this->offer->first()->discount / 100) : $this->price,
'quantity' => $this->quantity,
'inStock' => $this->inStock(),
'status' => $this->status,
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
];
}
The controller:
public function show($id)
{
try {
$product = new ProductResource(Product::findOrFail($id));
return $product->test;
// return JsonResponse::respondSuccess(trans(JsonResponse::MSG_SUCCESS), $product);
} catch (\Exception $e) {
return JsonResponse::respondError($e->getMessage());
}
}
when I use the test value inside the controller it's not returned, although it returned when I call it from Postman.
What the problem here?
$product is an instance of ProductResource And of course there is no test property on that class. And it does not implement the magic __get method.
So what you can do is either use $product['test'] cause it implements ArrayAccess or first you can do $product = (new ProductResource(Product::findOrFail($id)))->toArray($request); Then again you can use $product['test'] to get the test value.
I have below type of json in my laravel request, I want to validate json object key in my laravel request file. I want to validate title value is required of data json. I found solution but it's for controller, I want to validate it in my request file
{"ID":null,"name":"Doe","first-name":"John","age":25,"data":[{"title":"title1","titleval":"title1_val"},{"title":"title2","titleval":"title2_val"}]}
Why not use Validator
$data = Validator::make($request->all(), [
'ID' => ['present', 'numeric'],
'name' => ['present', 'string', 'min:0'],
'first-name' => ['present', 'string', 'min:0',],
'age' => ['present', 'numeric', 'min:0', 'max:150'],
'data' => ['json'],
]);
if ($data->fails()) {
$error_msg = "Validation failed, please reload the page";
return Response::json($data->errors());
}
$json_validation = Validator::make(json_decode($request->input('data')), [
'title' => ['present', 'string', 'min:0']
]);
if ($json_validation->fails()) {
$error_msg = "Json validation failed, please reload the page";
return Response::json($json_validation->errors());
}
public function GetForm(Request $request)
{
return $this->validate(
$request,
[
'title' => ['required'],
],
[
'title.required' => 'title is required, please enter a title',
]
);
}
public function store(Request $request)
{
$FormObj = $this->GetForm($request);
$FormObj['title'] = 'stackoveflow'; // custom title
$result = Project::create($FormObj); // Project is a model name
return response()->json([
'success' => true,
'message' => 'saved successfully',
'saved_objects' => $result,
], 200);
}
I'm passing data to these functions via Axios/Vue. The Eloquent interactions work perfectly. When I store (i.e. create a new call) the resource returns as expected. When I update a record, it updates in the database, however, I get a blank response. In other words the return new CallResource($call) returns nothing. I can't work out where I've gone wrong.
public function store(Request $request)
{
$call = $this->validate($request, [
'title' => 'required',
'job_id' => 'required',
'location' => 'required',
'starts' => 'required|date|before:ends',
'ends' => 'required|date|after:starts',
'rate' => 'required'
]);
$call = Call::create($call);
return new CallResource($call);
}
public function update(Request $request, $id)
{
$data = $this->validate($request, [
'title' => 'required',
'job_id' => 'required',
'location' => 'required',
'starts' => 'required|date|before:ends',
'ends' => 'required|date|after:starts',
'rate' => 'required'
]);
$call = Call::find($id);
$call->update($data);
return new CallResource($call);
}
The call resource is really simple
class CallResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function toArray($request)
{
return [
'id' => $this->id,
'title' => $this->title,
'location' => $this->location,
'starts' => $this->starts,
'ends' => $this->ends,
'rate' => $this->rate
];
}
}
Laravel 5.6 fyi.
Everytime I need to test method as authenticated user, I always insert role table because it has relationship with user table, then create new user.
Like this below code.
<?php
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use App\Models\User;
use App\Models\Role;
class CouponsTest extends TestCase
{
/**
* A basic test example.
*
* #return void
*/
use DatabaseMigrations;
//use WithoutMiddleware;
public function test_index_coupon(){
//factory(App\Models\Coupon::class, 5)->create()->toArray();
DB::table('roles')->insert([
['id' => 1, 'name' => 'customer'],
['id' => 2, 'name' => 'partner'],
['id' => 3, 'name' => 'admin'],
]);
$user = User::create([
'id' => 3,
'password' => bcrypt('123456789'),
'email' => 'admin#domain.co.id',
'role_id' => '3',
'status' => 'confirmed',
'balance' => 0,
]);
$this->actingAs($user)->visit('admin/coupons')->seePageIs('admin/coupons');
}
public function test_create_coupon()
{
DB::table('roles')->insert([
['id' => 1, 'name' => 'customer'],
['id' => 2, 'name' => 'partner'],
['id' => 3, 'name' => 'admin'],
]);
$user = User::create([
'id' => 3,
'full_name' => 'Admin Full Name',
'password' => bcrypt('123456789'),
'email' => 'admin#domain.co.id',
'role_id' => '3',
'status' => 'confirmed',
'balance' => 0,
]);
$this->actingAs($user)->visit('admin/coupons/create')->seePageIs('admin/coupons/create');
}
}
I know this is bad practice.
How should my code looks like to follow DRY principle?
It is common to use ModelFactory.php and define factories for your models.
You can also pass arguments to those models.
$factory->define(App\User::class, function (Faker\Generator $faker) {
static $password;
return [
'name' => $faker->name,
'email' => $faker->unique()->safeEmail,
'password' => $password ?: $password = bcrypt('secret'),
'remember_token' => str_random(10),
'role_id' => factory(App\Role::class)->create()->id,
];
});
$factory->define(App\Role::class, function(Faker\Generator $faker){
return [
'name' => $faker->name,
];
}
You can then do the following in your test:
$role = factory(App\Role::class)->create(['name' => 'admin']);
$adminUser = factory(App\User::class)->create(['role_id' => $role->id]);
Which if needed in many tests can be done once in your tests setup() method and define your admin user as protected variable of your test class .
Keep in mind you should probably also use DatabaseTransactions; trait in your test class in order to remove any entries created by the commands above at the end of your tests.