apple / app-store-server-library-python

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Support stubbing AppStoreServerAPIClient for unit tests

WFT opened this issue · comments

Feature request: I'd like the ability to create mock responses to API client calls which will be returned by the next call to a specific client method.

Use case

I would like to do unit testing for my server's use of the library. I want to test the following features of my server:

  1. Customer service requests to extend subscription renewals
  2. Requests to get the latest subscription status of a customer (e.g. for testing my handling of older clients with ReceiptUtility)

Example code

Here's an example test one could write:

from appstoreserverlibrary import testing as appstore

replace_server_api_client(appstore.TestingAPIClient(...))

def test_customer_service_extension(client):
    def callback(original_transaction_id: str, extend_renewal_date_request: ExtendRenewalDateRequest) -> ExtendRenewalDateResponse:
        assert stuff_about_the_request(original_transaction_id, extend_renewal_date_request)
        return ExtendRenewalDateResponse(...)

    with appstore.client_stubber as stubber: # Asserts on leaving the `with` that the expected responses were consumed
        stubber.extend_subscription_renewal_date.add_response(callback)
        response = client.post('/api/customer-service/app-store/extend')
        assert response.code == 200

    # Check that our server is properly handling saving the effectiveDate to the database
    response = client.get('/api/app-store/renewal-date')
    assert response.code == 200
    assert has_been_extended(response)

@WFT currently how the unit tests are constructed, either directly or through subclassing you can override the direct method used to make the HTTP call, do any validation required on the inputs, and then return any arbitrary response

def get_client_with_body(self, body: str, expected_method: str, expected_url: str, expected_params: Dict[str, Union[str, List[str]]], expected_json: Dict[str, Any], status_code: int = 200):
signing_key = read_data_from_binary_file('tests/resources/certs/testSigningKey.p8')
client = AppStoreServerAPIClient(signing_key, 'keyId', 'issuerId', 'com.example', Environment.LOCAL_TESTING)
def fake_execute_and_validate_inputs(method: bytes, url: str, params: Dict[str, Union[str, List[str]]], headers: Dict[str, str], json: Dict[str, Any]):
self.assertEqual(expected_method, method)
self.assertEqual(expected_url, url)
self.assertEqual(expected_params, params)
self.assertEqual(['User-Agent', 'Authorization', 'Accept'], list(headers.keys()))
self.assertEqual('application/json', headers['Accept'])
self.assertTrue(headers['User-Agent'].startswith('app-store-server-library/python'))
self.assertTrue(headers['Authorization'].startswith('Bearer '))
decoded_jwt = decode_json_from_signed_date(headers['Authorization'][7:])
self.assertEqual('appstoreconnect-v1', decoded_jwt['payload']['aud'])
self.assertEqual('issuerId', decoded_jwt['payload']['iss'])
self.assertEqual('keyId', decoded_jwt['header']['kid'])
self.assertEqual('com.example', decoded_jwt['payload']['bid'])
self.assertEqual(expected_json, json)
response = Response()
response.status_code = status_code
response.raw = BytesIO(body)
response.headers['Content-Type'] = 'application/json'
return response
client._execute_request = fake_execute_and_validate_inputs
return client
, or if you are looking for a higher level override, _make_request can be overridden which takes the request and response before they are parsed to/from JSON. This could be extended I believe to create the stubbed behavior you are describing

@alexanderjordanbaker Yeah I think that makes sense. In my codebase I've just been using a general-purpose HTTP mocking library (https://github.com/gabrielfalcao/HTTPretty -- wish it were a little more maintained). I think that to make a really great testing experience though, it'd be nice if we could get it into this library.