Skip to content

Testing Your Function Block

Function blocks interact with real network devices -- testing locally catches bugs before they reach production. A few well-placed tests save hours of debugging on live infrastructure.

Quick Setup

Install the test dependencies as optional extras:

uv add --optional test pytest pytest-asyncio neops-remote-lab

Add the pytest configuration to your pyproject.toml:

[tool.pytest.ini_options]
asyncio_mode = "auto"
asyncio_default_fixture_loop_scope = "function"

Note

The full pyproject.toml from the Setup page already includes this configuration and the test dependencies.

With asyncio_mode = "auto", every async def test runs as async automatically — no need for @pytest.mark.asyncio on each function. The SDK decorators (@fb_test_case, @fb_test_case_with_lab) also handle this internally.

Writing a Standalone Test

The pattern for testing a function block is straightforward:

  1. Build a WorkflowContext -- the data envelope your function block receives at runtime.
  2. Instantiate your function block class.
  3. Call execute_function_block() with parameters and context.
  4. Assert against the result.

Start with a helper that creates a minimal context:

def _create_context() -> WorkflowContext:
    return WorkflowContext(
        JobExecutionContextDto(devices=[], deviceGroups=[], interfaces=[]),
        run_on="global",
    )

WorkflowContext is the data envelope that neops passes to your function block at runtime — it contains device info, credentials, and workflow metadata. For unit tests of a simple block like Echo, an empty context is enough since the block does not access any device data.

Now write the actual test:

async def test_echo_returns_same_text() -> None:
    block = Echo()
    result = await block.execute_function_block(
        params=EchoParameters(message="hello"),
        context=_create_context(),
    )

    assert result.success
    assert result.data is not None
    assert result.data.echoed_message == "hello"

The test creates an Echo instance, executes it with text="hello", and verifies that the result is successful and contains the expected output. Run it with:

pytest -v

Tip

execute_function_block() is an async method, so every test function needs async def. With asyncio_mode = "auto" this works out of the box — no extra decorator required.

The Decorator Shortcut

For quick smoke tests, the SDK provides @fb_test_case -- a decorator that attaches test cases directly to your function block class. Pytest discovers and runs them automatically.

@fb_test_case(
    "Echo test",
    EchoParameters(text="hello"),
    context=ctx,
    assertions=[lambda r: r.success],
)
class Echo(FunctionBlock[EchoParameters, EchoResult]):
    ...

The assertions parameter takes a list of check functions. Each receives the result and should return True if the check passes. lambda r: r.success is shorthand for "check that the result was successful." You could also write it as a regular function:

def check_success(result):
    return result.success

Behind the scenes, the decorator generates a test function and registers it in the module. You get the same result as writing the test by hand, with less boilerplate.

When to use standalone tests vs. the decorator

Use standalone tests when you need complex setup, multiple assertions across different scenarios, or shared fixtures. Use @fb_test_case for straightforward pass/fail checks that live next to the function block definition.

Testing with Real Devices

The SDK includes @fb_test_case_with_lab for integration testing against actual network equipment. The remote lab provisions Netlab / Containerlab topologies on demand -- Cisco IOL routers, FRR instances, and more -- so your function blocks run against genuine device responses.

To enable it:

  1. Set the REMOTE_LAB_URL environment variable to your lab endpoint.
  2. Decorate your function block with @fb_test_case_with_lab, specifying the parameters and assertions.
  3. The decorator handles topology provisioning, device context creation, and teardown.
export REMOTE_LAB_URL=https://lab.your-neops-instance.example.com

Note

Remote lab tests require network access to the lab environment. They are best suited for CI pipelines or dedicated test environments. See the full Testing section for setup details and topology configuration.

Run Your Tests

Execute the full test suite with verbose output:

pytest -v

Expected output looks like:

========================= test session starts ==========================
collected 2 items

test_echo.py::test_echo_returns_same_text PASSED                 [ 50%]
test_echo.py::test_echo_handles_empty_string PASSED               [100%]

========================= 2 passed in 0.12s ============================

You've completed the getting started guide. Explore the deep dives for more detail:

  • Function Blocks -- parameters, results, registration, and lifecycle
  • Device Connections -- opening sessions, running commands, parsing output
  • Testing -- remote lab setup, fixtures, and advanced assertions