Overview
Considering that Maybern is in the business of automating core calculations for managing private funds, data accuracy is paramount. The integration testing suite ensures that data we generate is accurate and consistent across all customers—we should always compute and view data the same way day over day unless an expected code change is made. Integration tests are designed to be flexible so we can layer in more tests as we add more features and customers.Testing Customers
Long Lived Test Customers
Previously, all integration tests would create a new customer per test run, either by loading an Excel sheet or JSON template. This gave us a lot of coverage, but did not cover scenarios such as migrations or changes to existing customers. Long Lived Test Customers are customers that have tests run on them on a regular basis, rather than being recreated from scratch for each test run. These customers are automatically set up with delete protection. You can see the list of Long Lived Test Customers in sandbox at/internal/integration-testing/configurations.
Job Types
We currently have three types of integration tests. They differ in how the test customer is chosen, but all three run the same set of validation checks using the same set of expected data.V1 (Excel - Legacy)
- Loads data via shadow into new test customers
- Runs validation checks on the loaded customers
- Checks shadow load specific logic along with writes and reads across the app
Long Lived Customer Validation
- Runs validation checks on the long lived customers
- Primary purpose is to see if a migration has broken something
Long Lived Customer Reload and Validate
- Export long lived customer
- Reload into fresh test customer
- Run validation checks on the fresh test customer
- Validates event export and loading logic along with writes and reads across the app
Job Cadences
Different customers run at different cadences throughout the day. We currently have tests that run once, twice, or four times a day. Customer configs are grouped by their cadence type. When we run a specific cadence, we run tests for that cadence and all more frequent ones—so a run that triggers the once-a-day group will also run the twice and four-times-a-day groups. The jobs that run integration tests are defined in Terraform.Test Environments
| Environment | Cadence | Golden Datasets Location |
|---|---|---|
| PR Testing | Every push/merge | GitHub Repo |
| Sandbox | 4 times/day | S3 |
| Staging | 1 time/day | S3 |
Validation Types
Transaction Integration Tests
Compare how features are computed and persisted into the DB.API Integration Tests
Compare the data surfaced to end users via API.Export Tests
Ensure that a customer can be exported at the end of a run.Workflow
Breaking down a typical integration test run:- Read and validate transactions in parallel by type and customer
- Read and validate API data in parallel by type, sample number, and customer
- Export template data by customer in parallel (if configured)
What “Read and Validate” Means
- Read: Query the database for specified set of transactions, hit an API, etc. (depending on test type) against the newly provisioned customer
- Validate: Compare the exported data against a snapshot of that exact same test from a previous integration test
Debugging
When debugging integration tests, understand the toolkit we have for understanding what went wrong.Individual Test Artifacts
- Actual data: Export from the current run
- Expected data: Snapshot from the last successful run
- Comparison data: Diff between the actual and expected data
System-Level Tracking
- PRs are labeled with any tests that may have been updated (
integration-test-api,integration-test-transaction, etc.) - We track all git commits that go into a test run
- We compare test results against previous integration tests to identify new failures, existing failures, or resolved failures
- We compare enabled feature flags against the last successful run to identify flag changes
Models
Configuration Models
- IntegrationTestingConfiguration: Defines the set of customers/templates to run in an integration test
Queue Models
- TestingQueue: Tracks the type of integration test to run (for the cron to pick up)
- TestingQueueItem: Tracks an individual customer/test to run associated with the test type
Report Models
- TestingReport: Tracks a full integration test
- IndividualTestingReport: Tracks a single test within an integration test
Misc Models
- TestingReportIssue: Tracks an issue found in an integration test that is long running
Local Development
To load customers for testing, run the following commands:Setup Backend Server and Workers
V1 Test
V2 Test
maybern/integration_testing_ci_config.json. The feature flags specified in the config will be enabled for the test run.
API Integration Tests Deep Dive
Adding an API Test
API integration tests are defined inserver/apps/integration_testing/private/services/api_testing/api_tests in files roughly named as {app_name}_tests.py (e.g., fees_tests.py).
All API tests inherit from GenericAPITest, which is defined in server/apps/integration_testing/private/services/api_testing/api_test_definition.py.
Not all APIs need an API test. We primarily use them to test APIs that operate on top of or read from transaction data. A simple CRUD API that’s a snapshot of a customer configuration probably doesn’t need one.
KNOWN_UNTESTED_INTENTIONALLY_SKIPPED_GET_APIS in test_check_all_get_apis_tested.py.
Creating an API Test
To add an API test (that does not return GenericTableData):- Create a new class inheriting from
GenericAPITest - Specify three generic parameters: the API class itself, the param dataclass, and the response dataclass
- Implement the abstract method
get_api_test_request_list
dataclass_name field matching the name of the dataclass and should be added to the GenericAPITestParamData annotated union in the api_test_definition.py file.
GenericAPITableTest
Many APIs returnGenericTableData, so we introduced GenericAPITableTest which automatically sorts the rows in the table for deterministic ordering. Use this for APIs that return GenericTableData.
Post-Processing Responses
Many APIs require post-processing to ensure deterministic results (sorting, rounding, etc.). Overrideget_api_test_response_content_for_comparison_based_on_status_code and use:
_deserialize_response_contentto deserialize the API response- Process the response
serialize_responseto reserialize
Running API Integration Tests
- Store expected results in
backend/api-testingas<customer_name>_api_expected_results.json - Copy files from S3 at
maybern-dev-infra-config/api-testing, or generate locally:
- Run the comparison:
Viewing Failed Test Results
If tests fail, the diffs are output to:backend/api-testing/<customer_name>_api_diff_results.jsonbackend/api-testing/<customer_name>_api_diff_results.yaml
expected_actual_response_diffactual_expected_response_diffactual_responseexpected_response
maybern-dev-infra-config/api-testing/failed-test-results. Use your PR number to locate the diffs for your branch.