# Testing REST connections

REST mocks - responses, status codes, headers, sequential calls and request matching.

When your Zato service calls external REST APIs - whether it's a CRM, payment gateway, or any other HTTP endpoint - you need to test that logic without making real network calls. This page shows how to mock REST responses so your tests run fast, work offline, and don't require API credentials.

**Note:** If you're new to unit testing with Zato, check the [tutorial](https://zato.io/docs/api-testing/tutorial.html) first.

## Basic usage {#basic-usage}

Use `set_response()` to configure what a REST connection returns:

```python
from zato_testing import ServiceTestCase
from myapp.services import GetCustomer

class TestGetCustomer(ServiceTestCase):

    def test_returns_customer(self):

        # Mock the REST connection
        self.set_response('crm.api', {'id': 'CUST-001', 'name': 'Acme Corp'})

        service = self.invoke(GetCustomer, customer_id='CUST-001')

        self.assertEqual(service.response.payload['name'], 'Acme Corp')
```

The connection name must match exactly what your service uses in `self.rest['connection-name']`.

## Response options {#response-options}

### Status codes {#status-codes}

Set HTTP status codes with the `status_code` parameter:

```python
# Success with 201 Created
self.set_response('api', {'id': '123'}, status_code=201)

# Error response
self.set_response('api', {'error': 'Not found'}, status_code=404)
```

### Headers {#headers}

Include response headers:

```python
self.set_response('api', {'data': 'value'}, headers={
    'X-Request-Id': 'abc-123',
    'X-Rate-Limit-Remaining': '99'
})
```

### HTTP methods {#http-methods}

By default, responses apply to GET requests. Specify other methods:

```python
# POST request
self.set_response('api', {'created': True}, method='POST')

# PUT request
self.set_response('api', {'updated': True}, method='PUT')

# DELETE request
self.set_response('api', {'deleted': True}, method='DELETE')
```

## Sequential responses {#sequential-responses}

When a service calls the same connection multiple times, provide a list:

```python
def test_pagination(self):

    # First call returns page 1, second call returns page 2
    self.set_response('api', [
        {'items': ['a', 'b'], 'page': 1, 'has_more': True},
        {'items': ['c', 'd'], 'page': 2, 'has_more': False}
    ])

    service = self.invoke(FetchAllPages)

    self.assertEqual(service.response.payload['total_items'], 4)
```

## Request matching {#request-matching}

Return different responses based on request data:

```python
def test_different_actions(self):

    # Response for create action
    self.set_response('api',
        response={'id': 'NEW-001', 'action': 'created'},
        request={'action': 'create', 'name': 'New Item'}
    )

    # Response for update action
    self.set_response('api',
        response={'id': 'EXIST-001', 'action': 'updated'},
        request={'action': 'update', 'id': 'EXIST-001'}
    )

    # Service sends create request - gets create response
    service = self.invoke(ProcessItem, action='create', name='New Item')
    self.assertEqual(service.response.payload['action'], 'created')
```

### Multiple requests, same response {#multiple-requests-same-response}

Match multiple request patterns to the same response:

```python
self.set_response('api',
    response={'status': 'ok'},
    request=[
        {'type': 'ping'},
        {'type': 'health'},
        {'type': 'status'}
    ]
)
```

## Testing RESTAdapter {#testing-restadapter}

For services using [RESTAdapter](https://zato.io/docs/dev/rest/rest-adapter.html), `set_response` mocks the data that `map_response` receives:

```python
from zato.server.service import RESTAdapter

class GetFlight(RESTAdapter):
    name = 'flight.get'
    conn_name = 'flight.ops'
    input = 'flight_id'

    def get_params(self):
        return {'id': self.request.input.flight_id}

    def map_response(self, data):
        return {
            'flight': data['flight_number'],
            'status': data['current_status']
        }
```

```python
class TestGetFlight(ServiceTestCase):

    def test_maps_response(self):

        self.set_response('flight.ops', {
            'flight_number': 'FL-123',
            'current_status': 'boarding'
        })

        service = self.invoke(GetFlight, flight_id='FL-123')

        self.assertEqual(service.response.payload['flight'], 'FL-123')
        self.assertEqual(service.response.payload['status'], 'boarding')
```

## Missing responses {#missing-responses}

If a service calls a connection without a configured response, the framework raises an exception:

```text
ValueError: No REST response configured for connection 'crm.api'.
Use set_response('crm.api', <response>) to configure it.
```

This helps catch missing mocks early.

## Learn more {#learn-more}

- [API testing](https://zato.io/docs/api-testing/) - Tests in plain English, run against real or mocked connections
- [API testing tutorial](https://zato.io/docs/api-testing/tutorial.html) - A complete test suite, from the first scenario to the report
- [Testing SQL connections](https://zato.io/docs/api-testing/sql.html) - Mock database queries and the rows they return
- [Testing LDAP connections](https://zato.io/docs/api-testing/ldap.html) - Mock directory lookups and the entries they find
- [Testing Microsoft 365](https://zato.io/docs/api-testing/ms365/) - Mock mailboxes, calendars, SharePoint, Teams and the rest
- [Testing service invocations](https://zato.io/docs/api-testing/services.html) - Services that call other services, with the callees mocked
- [Testing Jira connections](https://zato.io/docs/api-testing/jira.html) - Mock Jira API calls and the issues they return
- [Tests in CI/CD](https://zato.io/docs/api-testing/ci-cd.html) - Running the suite in a pipeline and reading its output
