Testing¶
m
provides several utilities to help us test our actions. We can start with
this example
import pytest
from m.github.actions import Action
from m.testing import ActionStepTestCase as TCase
from m.testing import run_action_test_case
from pytest_mock import MockerFixture
from pkg.actions import actions
@pytest.mark.parametrize(
'tcase',
[
TCase(
name='test_id',
py_file=f'src/pkg/main.py',
inputs={
'INPUT_ARG_A': 'val_a',
'INPUT_ARG_B': 'val_b',
},
expected_stdout='Anything we print to stdout',
outputs=['some-output=some_value'],
),
],
ids=lambda tcase: tcase.name,
)
def test_m_gh_actions_api(tcase: TCase, mocker: MockerFixture) -> None:
run_action_test_case(mocker, tcase)
def test_actions_instance() -> None:
assert isinstance(actions, Action)
assert actions.name == 'Action Name'
Important
This is not required but it is recommended to have this in the __init__.py
for
the root of the tests.
from m.testing import block_m_side_effects, block_network_access
block_m_side_effects()
block_network_access()
This will make sure that our tests do not make any calls to the internet and prevents our code from writing files. Instead it will force us to create mocks.
Testing API¶
ActionStepTestCase
¶
Bases: BaseModel
Defines a test case for an action.
Useful in the parametrization of several use cases for an action.
Attributes:
Name | Type | Description |
---|---|---|
name |
str
|
Unique name for the test case. This is used as the identifier for a test case so that test failures may be easier to spot. |
py_file |
str
|
Path to the python file to run. |
inputs |
dict[str, str]
|
|
exit_code |
int
|
The expected exit code (default: 0). |
expected_stdout |
str
|
The expected stdout (default: empty). |
errors |
list[str]
|
Errors may be noisy, specify strings that are expected to be in stderr in an array of strings. |
outputs |
list[str]
|
|
file_write_side_effect |
Any | None
|
Defaults to |
Source code in m/testing/testing.py
block_m_side_effects()
¶
Blocks functions that have side effects.
This function overrides the definition of m
so that we do not accidentally
try write a lot of files or create/move directories.
The pathlib.Path.mkdir function should only be blocked while developing
tests. It is a reminder that we haven't mocked the function yet. If we want
to get this reminder then add touch m/.m/pytest-ran
after the tests run
locally.
Returns:
Type | Description |
---|---|
dict[str, Any]
|
A dictionary with references to the original functions that were overridden in case these are needed. |
Source code in m/testing/testing.py
block_network_access()
¶
Blocks network access for all tests.
This function overrides the definition of socket
so that
we do not accidentally try to run tests that make network calls. If our tests
do not depend on other local services it is a good idea to call this before
any of our tests runs.
Otherwise we may want to modify this function to allow certain hosts to be called. (PRs welcomed).
Source code in m/testing/testing.py
mock(func_name)
¶
run_action_step(mocker, *, py_file, exit_code, env_vars, file_write_side_effect=None)
¶
Execute an action step in a test.
This function expects the inputs to the script to be provided via environment
variables of the form INPUT_[SOME_NAME]
. The script will write the outputs
to the file FAKE_GITHUB_OUTPUT.txt
. We can verify the contents of the file
by looking at the 3rd output from the function. This is a dictionary mapping
file names to contents. Please note that this testing function mocks
m.core.rw.write_file to obtain the file contents.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
mocker |
MockerFixture
|
A reference to the pytest |
required |
py_file |
str
|
The full path to the file that Github Actions will run. |
required |
exit_code |
int
|
The expected exit code of the action. |
required |
env_vars |
dict[str, str]
|
A dictionary of the environment variables that the action will receive. |
required |
file_write_side_effect |
Any | None
|
This can be provided if we need to modify the behavior of m.core.rw.write_file. This is useful if we want to test cases in which a file failed to write. |
None
|
Returns:
Type | Description |
---|---|
tuple[str, str, dict[str, str]]
|
The standard out, standard error, and files written by m.core.rw.write_file. |
Source code in m/testing/conftest.py
run_action_test_case(mocker, tcase)
¶
Execute an action step test case.
This is a commodity wrapper to help us run the action tests case. If we need more control over the assertions we can then copy and modify the implementation.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
mocker |
MockerFixture
|
A reference to the pytest |
required |
tcase |
ActionStepTestCase
|
The test case. |
required |