- https://docs.python.org/3/library/unittest.mock.html#where-to-patch
- https://stackoverflow.com/questions/893333/multiple-variables-in-a-with-statement
- https://docs.python.org/3/reference/compound_stmts.html#the-with-statement
- https://news.ycombinator.com/item?id=36054868
- https://docs.python.org/3/library/sys.html#sys.settrace
- https://github.com/kunalb/panopticon
General Recommendations
When writing tests for Prowler provider checks, follow these guidelines to maximize coverage across test scenarios:- Zero Findings Scenario: Develop tests where no resources exist. Prowler returns zero findings if the audited service lacks the required resources.
-
Positive and Negative Outcomes:
Create tests that generate both a passing (
PASS) and a failing (FAIL) result. - Multi-Resource Evaluations: Design tests with multiple resources to verify check behavior and ensure the correct number of findings.
Test File Naming Conventions
Test files follow the pattern{service}_{check_name}_test.py for checks and {service}_service_test.py for services.
Duplicate Names Across Providers
When a test file name already exists in another provider, add your provider prefix to avoid conflicts. A GitHub Action will fail if duplicate names are detected. Example: Ifkms_service_test.py already exists in AWS, name your Oracle Cloud test oraclecloud_kms_service_test.py.
Running Prowler Tests
To execute the Prowler test suite, install the necessary dependencies listed in thepyproject.toml file.
Prerequisites
If you have not installed Prowler yet, refer to the developer guide introduction.Executing Tests
Navigate to the project’s root directory and execute:pytest -n auto -vvv -s -x
Alternatively, use:
Makefile with make test.
Other Commands for Running Tests
- Running tests for a provider:
pytest -n auto -vvv -s -x tests/providers/<provider>/services - Running tests for a provider service:
pytest -n auto -vvv -s -x tests/providers/<provider>/services/<service> - Running tests for a provider check:
pytest -n auto -vvv -s -x tests/providers/<provider>/services/<service>/<check>
Refer to the pytest documentation for more details.
AWS Service Dependency Table (CI Optimization)
To optimize CI pipeline execution time, the GitHub Actions workflow for AWS tests uses a service dependency table that determines which tests to run based on changed files. This ensures that when a service is modified, all dependent services are also tested.How It Works
The dependency table is defined in.github/workflows/sdk-tests.yml within the “Resolve AWS services under test” step. When files in a specific AWS service are changed:
- Tests for the changed service are run
- Tests for all services that depend on the changed service are also run
ec2 service, tests will also run for dlm, dms, elbv2, emr, inspector2, rds, redshift, route53, shield, ssm, and workspaces because these services use the EC2 client.
Current Dependency Table
The table maps a service (key) to the list of services that depend on it (values):When to Update the Table
You must update the dependency table when:-
A new check or service uses another service’s client: If your check imports a client from another service (e.g.,
from prowler.providers.aws.services.ec2.ec2_client import ec2_clientin a non-ec2 check), add your service to the dependent services list of that client’s service. - A service relationship changes: If you remove or add a service client dependency in an existing check, update the table accordingly.
How to Update the Table
- Open
.github/workflows/sdk-tests.yml - Find the
dependentsdictionary in the “Resolve AWS services under test” step - Add or modify entries as needed
- Update this documentation page (
docs/developer-guide/unit-testing.mdx) to reflect the changes in the Current Dependency Table section above
newservice service that imports ec2_client, add newservice to the ec2 entry:
AWS Testing Approaches
For AWS provider, different testing approaches apply based on API coverage based on several criteria.Prowler leverages and contributes to theMoto library for mocking AWS infrastructure in tests.
-
AWS API Calls Covered by Moto:
- Service Tests:
@mock_aws - Checks Tests:
@mock_aws
- Service Tests:
-
AWS API Calls Not Covered by Moto:
- Service Tests:
mock_make_api_call - Checks Tests: MagicMock
- Service Tests:
-
AWS API Calls Partially Covered by Moto:
- Service Tests:
@mock_awsandmock_make_api_call - Check Tests:
@mock_awsandmock_make_api_call
- Service Tests:
AWS Check Testing Scenarios
The following section provides examples for each testing scenario. The primary distinction between these scenarios depends on whether the Moto library covers the AWS API calls made by the service. You can review the supported API calls here.AWS Check Testing Approach
For AWS test examples, we reference tests for theiam_password_policy_uppercase check.
This section is categorized based on Moto API coverage.
API Calls Covered by Moto
When the Moto library supports the API calls required for testing, use the@mock_aws decorator. This ensures that all AWS API calls within the decorated function are properly mocked while maintaining state within the test.
Handling API Calls Not Covered by Moto
If the IAM service required for testing is not supported by the Moto library, use MagicMock to inject objects into the service client.The example below demonstrates the IAM GetAccountPasswordPolicy API, which is covered by Moto, but is used for instructional purposes only.
Mocking Service Objects Using MagicMock
The following code demonstrates how to use MagicMock to create service objects.Ensuring Test Isolation with Mocked/Patched Objects
In all above scenarios, check execution must occur within the context of mocked or patched objects. This guarantees that the test only evaluates objects explicitly created within its scope, preventing interference from shared state or external dependencies.Handling Partially Covered API Calls
When a service requires API calls that are partially covered by the Moto decorator, additional mocking is necessary. In such cases, custom mocked API calls must be implemented alongside Moto to ensure full coverage. To achieve this, mock thebotocore.client.BaseClient._make_api_call function—the method responsible for making actual API requests to AWS—using mock.patch <https://docs.python.org/3/library/unittest.mock.html#patch>:
This example does not use Moto to simplify the setup.
However, if additional
moto decorators are applied alongside the patch, Moto will automatically intercept the call to orig(self, operation_name, kwarg).The source of the above implementation can be found here:Patch Other Services with Moto
Mocking Several Services
Since the provider is being mocked, multiple attributes can be configured to customize its behavior:cloudtrail_logs_s3_bucket_access_logging_enabled relies on both the CloudTrail and S3 clients, the test’s service mocking section should be structured as follows:
Patching vs. Importing
Properly understanding patching versus importing is critical for unit testing with Prowler checks. Given the dynamic nature of the check-loading mechanism, the process for importing a service client within a check follows this structured approach:-
<check>.py: -
<service>_client.py:
<service>_clientimported at<check>.py<service>_clientinitialised at<service>_client.py<SERVICE>imported at<service>_client.py
Additional Resources on Mocking Imports
For a deeper understanding of mocking imports in Python, refer to the following article: https://stackoverflow.com/questions/8658043/how-to-mock-an-importApproaches to Mocking a Service Client
1. Mocking the Service Client at the Service Client Level 2. Mocking a Service Client via Below Code Implementation Once all required attributes are configured for the mocked provider, it can be used as the service client for test execution:Mocking the service_client
-
When
<SERVICE>(set_mocked_aws_provider([<region>]))is mocked out usingmock.patch, it must be properly prepared before patching to ensure test consistency. -
At the point of patching, in
<service>_client.py, and sincemock.patchneeds to access said object and initialise it,<SERVICE>(set_mocked_aws_provider([<region>]))will be called again.
<service>_client.py at <check>.py, Python uses the mocked instance since the patch was applied at the correct reference point.
In the next section we will explore an improved approach to mock objects.
Mocking the Service and the Service Client at the Service Client Level
Mocking a Service Client via Below Code Implementation
Mocking the service and the service_client
set_mocked_aws_provider([<region>]) using mock.patch.
Later, when Python attempts to import the client at the check level, the execution continues usingfrom prowler.providers.<provider>.services.<service>.<service>_client. As a result of it being already mocked out, the execution will continue using service_client without getting into <service>_client.py.
Testing AWS Services
AWS service testing follows the same methodology as AWS checks: Verify whether the AWS API calls made by the service are covered by Moto. Execute tests on the service__init__ to ensure correct information retrieval.
While service tests resemble Integration Tests, as they assess how the service interacts with the provider, they ultimately fall under Unit Tests, due to the use of Moto or custom mock objects.
For detailed guidance on test creation and existing service tests, check the current AWS checks implementation.
GCP
GCP Check Testing Approach
Currently the GCP Provider does not have a dedicated library for mocking API calls. To ensure proper test isolation, objects must be manually injected into the service client using MagicMock. Mocking Service Objects Using MagicMock The following code demonstrates how to use MagicMock to create service objects for a GCP check test. This is a real-world implementation, adapted for instructional clarity.Testing GCP Services
The testing of Google Cloud Services follows the same principles as the one of Google Cloud checks. While all API calls must be mocked, attribute setup for API calls in this scenario is defined in the fixtures file, specifically within the fixtures file in themock_api_client function.
The following example presents a real testing class, but includes additional comments for educational purposes, explaining key concepts and implementation details.
BigQuery Service Test
-
Step 1: Identify the API Call for Dataset Retrieval
To determine how datasets are obtained, examine the API call used by the service. In this case, the relevant service call is:
self.client.datasets().list(projectId=project_id). -
Step 2: Mocking the API Call in the Fixture File
In the fixture file, mock this call in the
MagicMockclient, in the functionmock_api_client. - Step 3: Structuring the Mock Function The best approach for mocking is to adhere to the service’s existing format:
mock_api_<endpoint>_calls (endpoint refers to the first attribute pointed after client).
For BigQuery, the mock function is called mock_api_dataset_calls. Within this function, an assignment is made for use in the _get_datasets method of the BigQuery class:
Azure
Azure Check Testing Approach
Currently the Azure Provider does not have a dedicated library for mocking API calls. To ensure proper test isolation, objects must be manually injected into the service client using MagicMock. Mocking Service Objects Using MagicMock The following code demonstrates how to use MagicMock to create service objects for an Azure check test. This is a real-world implementation, adapted for instructional clarity.app_ensure_http_is_redirected_to_https_test.py
Testing Azure Services
The testing of Azure Services follows the same principles as the one of Google Cloud checks. All API calls are still mocked, but for methods that initialize attributes via an API call, use the patch decorator at the beginning of the class to ensure proper mocking. The following example presents a real testing class, but includes additional comments for educational purposes, explaining key concepts and implementation details.AppInsights Service Test

