Testing REST API
To ensure that your REST API resources work properly, cover them with functional tests. To simplify the creation of the functional test for REST API resources that conforms to JSON:API specification, the RestJsonApiTestCase test case was created. The following table contains the list of the most valuable methods in this class:
Method |
Description |
---|---|
request |
Sends a REST API request. |
options |
Sends the OPTIONS request. See options action. |
get |
Sends the GET request for a single entity. See get action. |
cget |
Sends the GET request for a list of entities. See get_list action. |
post |
Sends the POST request for an entity resource. See create action. If the second parameter is a file name, the file should be located in the |
patch |
Sends the PATCH request for a single entity. See update action. If the second parameter is a file name, the file should be located in the |
cpatch |
Sends PATCH request for a list of entities. See update_list action. If the second parameter is a file name, the file should be located in the |
delete |
Sends the DELETE request for a single entity. See delete action. |
cdelete |
Sends the DELETE request for a list of entities. See delete_list action. |
getSubresource |
Sends the GET request for a sub-resource of a single entity. See get_subresource action. |
postSubresource |
Sends the POST request for a sub-resource of a single entity. See add_relationship action. |
patchSubresource |
Sends the PATCH request for a sub-resource of a single entity. See update_relationship action. |
deleteSubresource |
Sends the DELETE request for a sub-resource of a single entity. See delete_relationship action. |
getRelationship |
Sends the GET request for a relationship of a single entity. See get_relationship action. |
postRelationship |
Sends the POST request for a relationship of a single entity. See add_relationship action. |
patchRelationship |
Sends the PATCH request for a relationship of a single entity. See update_relationship action. |
deleteRelationship |
Sends the DELETE request for a relationship of a single entity. See delete_relationship action. |
assertResponseContains |
Asserts that the response content contains the given data. If the first parameter is a file name, the file should be located in the |
assertResponseCount |
Asserts that the response contains the given number of data items. |
assertResponseNotEmpty |
Asserts that the response data are not empty. |
assertResponseNotHasAttributes |
Asserts that the response content does not contain the given attributes. |
assertResponseNotHasRelationships |
Asserts that the response content does not contain the given relationships. |
assertResponseValidationError |
Asserts that the response content contains one validation error and it is the given error. |
assertResponseContainsValidationError |
Asserts that the response content contains the given validation error. |
assertResponseValidationErrors |
Asserts that the response content contains the given validation errors and only them. |
assertResponseContainsValidationErrors |
Asserts that the response content contains the given validation errors. |
assertAllowResponseHeader |
Asserts |
assertMethodNotAllowedResponse |
Asserts response status code equals to 405 (Method Not Allowed) and |
dumpYmlTemplate |
Saves a response content to a YAML file. If the first parameter is a file name, the file is saved into the responses directory next to the PHP file that contains the test. |
getResourceId |
Extracts the JSON:API resource identifier from the response. For details, see JSON:API specification. |
getNewResourceIdFromIncludedSection |
Extracts the JSON:API resource identifier from the |
getRequestData |
Converts the given request to an array that can be sent to the server. The request can be a path to a file containing the request data or an array with the request data. If the request is a file name, the file should be located in the |
getResponseData |
Converts the given response to an array that can be used to compare it with a response received from the server. The given response can be a path to a file that contains the response data or an array with the response data. If the response is a file name, the file should be located in the |
getResponseErrors |
Extracts the list of errors from the JSON:API response. For details, see JSON:API specification. |
updateResponseContent |
Replaces all values in the given expected response content with the corresponding value from the actual response content when the key of an element is equal to the given key and the value of this element is equal to the given placeholder. If the first parameter is a file name, the file should be located in the |
getApiBaseUrl |
Returns the base URL for all REST API requests, e.g. |
appendEntityConfig |
Appends a configuration of an entity. This method is helpful when you create a general functionality and need to test it for different configurations without creating a test entity for each of them. Please note that the configuration is restored after each test, and thus, you do not need to do it manually. |
Note
By default, HATEOAS is disabled in functional tests, although it is enabled by default in production and API Sandbox. It was done to avoid cluttering up the tests with HATEOAS links. In case you want to enable HATEOAS for your test, use HTTP_HATEOAS server parameter, e.g. $this->cget(['entity' => 'products'], [], ['HTTP_HATEOAS' => true])
.
Testing Batch API
To simplify the creation of the functional test for REST Batch API resources that conforms to JSON:API specification, the RestJsonApiUpdateListTestCase test case was created. The following table contains the list of the most valuable methods in this class:
Method |
Description |
---|---|
processUpdateList |
Sends PATCH request for a list of entities (see update_list action) and executes all message queue processors required to process Batch API operation. |
Example of a functional test for Batch API:
public function testCreateEntities()
{
$this->processUpdateList(
Lead::class,
[
'data' => [
[
'type' => 'leads',
'attributes' => ['name' => 'New Lead 1']
]
]
]
);
$response = $this->cget(['entity' => 'leads'], ['fields[leads]' => 'name']);
$responseContent = $this->updateResponseContent(
[
'data' => [
[
'type' => 'leads',
'id' => '<toString(@lead1->id)>',
'attributes' => ['name' => 'Existing Lead 1']
],
[
'type' => 'leads',
'id' => 'new',
'attributes' => ['name' => 'New Lead 1']
]
]
],
$response
);
$this->assertResponseContains($responseContent, $response);
}
To simplify the creation of the functional test for synchronous REST Batch API resources that conforms to JSON:API specification, use the RestJsonApiSyncUpdateListTestCase test case.
Example of a functional test for synchronous Batch API:
public function testCreateEntities()
{
$response = $this->cpatch(
['entity' => 'leads'],
[
'data' => [
[
'type' => 'leads',
'attributes' => ['name' => 'New Lead 1']
]
]
],
['HTTP_X-Mode' => 'sync']
);
$responseContent = $this->updateResponseContent(
[
'data' => [
[
'type' => 'leads',
'id' => 'new',
'attributes' => ['name' => 'New Lead 1']
]
]
],
$response
);
$this->assertResponseContains($responseContent, $response);
}
Load Fixtures
You can use Doctrine and Alice fixtures:
class InventoryLevelTest extends RestJsonApiTestCase
{
protected function setUp()
{
parent::setUp();
$this->loadFixtures([@OroInventoryBundle/Tests/Functional/DataFixtures/inventory_level.yml']);
}
Fixture file:
dependencies:
- Oro\Bundle\WarehouseBundle\Tests\Functional\DataFixtures\LoadWarehouseAndInventoryLevels
Oro\Bundle\InventoryBundle\Entity\InventoryLevel:
warehouse_inventory_level.warehouse.1.product_unit_precision.product-1.primary_unit:
warehouse: '@warehouse.1'
productUnitPrecision: '@product-1->primaryUnitPrecision'
quantity: 10
You can use the dependencies
section if a fixture depends on another Doctrine or Alice fixtures. References are shared between Alice and Doctrine fixtures.
Alice References
You can use references in Alice fixtures:
@product-1
Use methods of properties with references:
@product-2->createdAt->format("Y-m-d\TH:i:s\Z")
YAML Templates
A YAML template is a regular YAML file. The only difference is that you can use references and fakers in values. They will be processed by Alice and replaced with the appropriate real values. For details, see the Alice documentation.
Assert the Expectations
Assert the expected response by using YAML templates.
A YAML template:
data:
-
type: inventorylevels
id: '@warehouse_inventory_level.warehouse.1.product_unit_precision.product-1.liter->id'
attributes:
quantity: '10'
relationships:
product:
data:
type: products
id: '@product-1->id'
productUnitPrecision:
data:
type: productunitprecisions
id: '@product_unit_precision.product-1.liter->id'
warehouse:
data:
type: warehouses
id: '@warehouse.1->id'
In php test:
public function testGetList()
{
$response = $this->cget(
['entity' => 'inventorylevels'],
[
'include' => 'product,productUnitPrecision',
'filter' => [
'product.sku' => '@product-1->sku',
]
]
);
$this->assertResponseContains('cget_filter_by_product.yml', $response);
}
YAML Templates for a Request Body
You can use an array with references for a request body:
public function testUpdateEntity()
{
$response = $this->patch(
['entity' => 'inventorylevels', 'product.sku' => '@product-1->sku'],
[
'data' => [
'type' => 'inventorylevels',
'id' => '<toString(@product-1->id)>',
'attributes' => [
'quantity' => '17'
]
],
]
);
}
Alternatively, you can store YAML in a .yml
file:
public function testCreateCustomer()
{
$this->post(
['entity' => 'customers'],
'create_customer.yml' // loads data from __DIR__ . '/requests/create_customer.yml'
);
}
Process Single Reference
To process a single reference, for example, to compare it with another value:
self::processTemplateData('@inventory_level.product_unit_precision.product-1.liter->quantity')
The processTemplateData
method can process a string, an array, or a YAML file.
Dump the Response into a YAML Template
When you develop new tests for REST API, it may be convenient to dump responses into a YAML template:
public function testGetList()
{
$response = $this->cget(
['entity' => 'products'],
['filter' => ['sku' => '@product-1->sku']]
);
// dumps response content to __DIR__ . '/responses/' . 'test_cget_entity.yml'
$this->dumpYmlTemplate('test_cget_entity.yml', $response);
}
Important
Do not forget to check references after you dump a response for the first time: there can be collisions if references have the same IDs.
Base Test Cases for Unit Tests
To simplify unit testing of API processors, you can use one of the following base classes:
Class |
Action |
---|---|