For a Laravel Test of my Product API I am trying to check if the right number of prices are listed for all products. The API returns Json and the prices are items in the products.*.prices arrays.
I am new to Laravel Test.
How do I loop through all the products and check their prices?
$response = $this->getJson('/api/product/');
$json_array = $response->json();
$test_passed = true;
for ($i = 0; $i < count($json_array); $i++) {
//for simplification I check here if all the products have 2 prices
$test_passed = $response->assertJsonCount(2, "product.$i.prices");
}
//check if all the test passed
$this->assertTrue($passed_test);
This works but it runs out of memory really fast. What am I doing wrong?
UPDATE:
I found in the Laravel API that AssertableJson has and ->each() method. I was trying to play around with this to make something like
$response->assertJson(fn (AssertableJson $json) =>
$json->each(fn (AssertableJson $json) =>
//2 will be dynamic in the final implementation.
$json->has('prices', 2)
)
);
This doesn't work, Apperently... I can't figure out how this ->each() works and there are no examples online to be found. All I can find are the Laravel docs, yet they give no example with nested arrarys.
Since you should be wiping your database clean for every test to start with a clean slate, I think the steps to take are
Product
models with an arbitrary amount of Price
models associated with themProduct
models as we specified in step 1.Product
has as many Price
models associated with them as we specified in step 1.public function test_products_api_returns_correct_amount_of_products()
{
// ARRANGE
Product::factory()->count(15)->create()
// ACT
$response = $this->getJson('/api/product/');
// ASSERT
$response->assertJsonCount(15, 'products');
}
public function test_products_api_returns_correct_amount_of_prices_for_each_product()
{
// ARRANGE
$counts = [1, 3, 5, 7, 10];
foreach ($counts as $count) {
Product::factory()
->has(Price::factory()->count($count)) // or ->hasPrices($count)
->create();
}
// ACT
$response = $this->getJson('/api/product/');
// ASSERT
foreach ($counts as $index => $count) {
$response->assertJsonCount($count, "products.$index.prices");
}
}
Here notice I'm using the key
value of the array in the foreach. This is not possible with the AssertableJson
syntax because the Closure does not accept a second parameter (unlike the Collection
's each
method where I could use the key if I wanted to with $collection->each(function ($item, $key) { ... });
.
The AssertableJson API is a bit limited so you cannot really use it for this test, unless you make a single type of product.
public function test_products_api_returns_correct_amount_of_prices_for_each_product()
{
// ARRANGE
Product::factory()
->count(15)
->has(Price::factory()->count(5))
->create();
}
// ACT
$response = $this->getJson('/api/product/');
// ASSERT
$response->assertJson(fn (AssertableJson $json) =>
$json->has('products', 15, fn (AssertableJson $product) =>
$product->count('prices', 5)->etc()
)
);
// or
$response->assertJson(fn (AssertableJson $json) =>
$json->has('products', 15, fn (AssertableJson $product) =>
$product->has('prices', 5, fn (AssertableJson $price) =>
// assertions about the products.*.prices.* array.
)->etc()
)
);
}
Another test you can create and that is sort of important for json apis is a test against the returned json structure. In your case it would look like this
public function test_products_api_returns_the_correct_structure()
{
// ARRANGE
... make all the product, prices, options, etc
// ACT
$response = $this->getJson('/api/product/');
// ASSERT
$response->assertJsonStructure([
'products' => [
'*' => [
'product_id',
'name',
'description',
'included',
'is_active',
'prices' => [
'*' => [
'price_id',
'weight',
'height',
'length',
'depth',
'pieces',
'color',
'price',
'start_time',
'end_time',
'sales' => [
'*' => [
'sale_id',
'sale_price',
'sale_start_time',
'sale_end_time',
],
],
],
],
'photos' => [
'*' => [
'photo_id',
'alt',
'path',
],
],
'properties' => [
'*' => [
'property_id',
'name',
'description',
'quantitative',
'position',
'is_active',
'properties_properties' => [
'*' => [
'properties_property_id',
'name',
'icon',
'path',
'attributes' => [
'*' => [
'product_id',
'properties_property_id',
'position',
'highlight',
],
],
],
],
],
],
'options' => [
'*' => [
'option_id',
'name',
'place_holder',
'position',
'is_active',
'options_options' => [
'*' => [
'options_option_id',
'name',
'position',
'price',
'is_active',
],
],
],
],
],
],
]);
}
I tested this last one against the json you posted (jsonblob.com/1087309750307405824) and it passes.