This guide walks you through testing in Infrahub using the Pytest plugin. You’ll learn to validate GraphQL queries, Jinja2 templates, and Transformations—all with easy-to-write YAML tests that require no Python coding.
. . . . .
We all write code, but how do we know the changes we make in the future won’t break something that used to work? That’s where testing becomes important.
The idea is to catch problems early, ideally before they reach production. In the Python world, one of the most common ways to do this is with a tool called pytest. It lets you write tests to check that your code behaves the way you expect and helps you catch issues before they become a bigger problem.
When working with Infrahub, testing is just as important. You might want to make sure your GraphQL queries are valid, your Jinja2 templates render correctly, or your transformations behave as expected.
Infrahub simplifies this by offering a pytest plugin that doesn’t require Python code; you define tests using plain YAML. This makes testing more accessible to teams across roles and speeds up the feedback loop during development.
These kinds of unit tests aren’t just about convenience, they help establish a production-ready automation system. With automated checks built into your process, every change is validated consistently, reducing the chance of something breaking unexpectedly. That consistency builds trust when your data behaves reliably, it’s easier for teams across your environment to work with confidence.
In this post, we’ll walk through how to use the Infrahub pytest plugin and cover the kinds of tests you can run, including syntax validation for GraphQL, Jinja2, and Transformation templates.
. . . . .
A Quick Recap
This post assumes you’re already familiar with Infrahub and specifically with Jinja2 Transformations. That said, even if you’re not fully familiar with them, you should still be able to follow along.
There are two YouTube videos that walk through the topics covered here in a video format:
- Building a Production-Grade Automation Platform With Infrahub – Part 1
- Building a Production-Grade Automation Platform With Infrahub – Part 2
And there’s a GitHub repository you can clone to try things out yourself.
In our previous post, we covered how to use Jinja2 with Infrahub by setting up a Transformation that takes data from Infrahub and renders it into plain text using a Jinja2 template. This process involves writing a GraphQL query to fetch the input data and passing it into the Jinja2 template. Everything is packaged into an .infrahub.yml
file inside a Git repository, which Infrahub can then import and use.
In this post, we’ll solely focus on how to write tests using the pytest Infrahub plugin. The good news is, you don’t need to know pytest, and you won’t even need to write any Python code. So let’s get to it.
. . . . .
Setting Up the Environment
Infrahub Installation
Before we begin, of course, we need an Infrahub instance. The easiest way to get it up and running is by using Docker. You can do this by running the following:
curl https://infrahub.opsmill.io | docker compose -p infrahub -f - up -d
This command takes a few minutes to pull the necessary images and start the applications. Once everything is running, open your browser and go to http://:8000. Use the default credentials admin for the username and infrahub for the password to log in.
If you want to stop and remove the containers later, you can run:
docker compose -p infrahub down
Cloning and Preparing the Repository
Once you have Infrahub up and running and can log in, the next step is to clone a repository that contains the necessary schemas, sample data, transformations, and tests.
This saves you the trouble of setting everything up from scratch. If you’re trying this out in your environment with your data, you can skip this step. This is mainly for readers who want to follow along with the post using the provided sample data.
However, keep in mind that Infrahub needs full access to the Git repository you’re working with. Since this is a public repository, you won’t be able to add it directly to Infrahub with full permissions unless you fork it or copy it to a repository you control.
You have two options:
- Fork the repository on GitHub and use your fork’s URL when adding it to Infrahub. Just click the Fork button at the top right of the GitHub page and create a copy under your account.
- Clone the repo and point it to your own Git remote. To do this, run the following commands. This example uses GitLab, but the remote URL will differ depending on which Git provider you use, such as GitHub or Bitbucket. Be sure to update the URL accordingly.
git remote remove origin
git remote add origin git@gitlab.com:yourname/infrahub-repo.git
git push -u origin --all

uv
.
uv add infrahub-sdk --extra all
pip install infrahub-sdk[all]
source .venv/bin/activate
Infrahubctl
To use infrahubctl
with our Infrahub instance, we first need to set up a few environment variables. This includes the API URL, username, and password. These variables let infrahubctl
authenticate and communicate with the Infrahub API.
export INFRAHUB_USERNAME=admin
export INFRAHUB_PASSWORD=infrahub
export INFRAHUB_ADDRESS=http://localhost:8000
Once that’s done, run the command infrahubctl
info, and if everything is set up correctly, you should see a connection status with a green tick confirming that infrahubctl
can connect to Infrahub.
. . . . .
Loading Schema and Data into Infrahub
Next, we’ll load the schema and data into Infrahub. The schema is already included in the repository you cloned earlier. To load it, run:
infrahubctl schema load schemas/
infrahubctl object load data/sites.yml
infrahubctl object load data/groups.yml
infrahubctl object load data/devices.yml
infrahubctl object load data/servers.yml
infrahubctl object load data

Jinja2 Transformation
A Jinja2 Transformation in Infrahub is a way to take data stored in Infrahub and render it into a text output, like a device configuration. This is done using a Jinja2 template combined with a GraphQL query to fetch the input data. We’re not covering this in detail here, just enough to understand the context for testing it. In the example repository we’ve been using throughout this post, under thetemplates
directory, you’ll find two key files. One is the GraphQL query (device_server.gql
), and the other is the Jinja2 template (arista_ntp.tpl.j2
).
query($device_name: String!) {
InfraDevice(name__value: $device_name) {
edges {
node {
name {
value
}
location {
node {
name {
value
}
servers {
edges {
node {
name {
value
}
}
}
}
}
}
}
}
}
}
! Specify NTP servers
{% for server in data.InfraDevice.edges[0].node.location.node.servers.edges %}
ntp server {{ server.node.name.value }}
{% endfor %}
{% if data.InfraDevice.edges[0].node.location.node.servers.edges|length > 0 %}
ntp enable
{% else %}
ntp disable
{% endif %}
device_name
, which lets us dynamically fetch data for a specific device. When we run this query inside the Infrahub GraphQL Sandbox, we get structured output containing the device name, its location, and associated servers. 
ntp enable
command might not be valid on Arista. The point of this example is not to reflect a real command but to demonstrate how we can handle scenarios where no NTP servers are defined for a location and adjust the output accordingly.
You can preview the rendered configuration directly from the CLI using infrahubctl
. Just run the following command to render the config for a specific device.
infrahubctl render arista_ntp device_name=atl-core-2
! Specify NTP servers
ntp server 01.ntp.atl.acme.com
ntp server 02.ntp.atl.acme.com
ntp enable
Why Test with the Pytest Plugin for Infrahub
So far, everything works, and we have a rendered configuration. But you might wonder, why would we need testing? To kick things off, let’s say someone else, or even you, comes in and makes a change to either the perfectly working GraphQL query or the Jinja2 Transformation. How do we know those changes are free of syntax errors? Sure, if we send a bad query to Infrahub, we’ll likely see an error in response, but ideally, we want to catch these issues as early as possible, before they make it into production or cause unexpected failures.Overview of the Infrahub Pytest Plugin
Infrahub allows users to define tests to ensure that Transformations and checks are working as intended. This can be an important step while writing Infrahub-related resources, as it makes sure they keep returning the same expected values over time and version bumps. These tests are based on pytest but do not require users to write any Python code. Tests can be run via a command line and the pytest executable, but they’re also integrated in the CI pipelines of proposed changes in Infrahub.Types of Test
The Infrahub test framework provides different types of tests to validate resources at different stages.- Smoke tests validate that resources are defined appropriately. They make sure that resources can be parsed and they’re free of syntax errors
- Unit tests validate that resources can be rendered or run. They take input and output values as parameters to assert that rendering or running a resource will give an expected result for a known input.
- Integration tests validate that resources work as intended by testing them against a live Infrahub instance. We have integration tests under the tests/integration directory in the repository. We won’t cover them in this post, but we’ll look into covering them in an upcoming post.
Declaring Tests
As mentioned, declaring tests doesn’t imply writing Python code. However, they do need to be defined in one or multiple YAML files. In a file, all tests must be listed under theinfrahub_tests
key.
Running Smoke Tests with Pytest in Infrahub
Under the tests/smoke directory, we have a test file that defines two smoke tests. The test types aregraphql-query-smoke
and jinja2-transform-smoke
, which are used to check the syntax of the GraphQL query and the Jinja2 template. Each test points to its corresponding file inside the templates
directory, ensuring that both resources are syntactically correct.
If you run the smoke tests using pytest tests/smoke/
command, you’ll see both tests pass successfully. Each dot in the output represents a test that passed. This confirms that both the GraphQL query and the Jinja2 template are free of syntax errors. 

Validating Templates with Pytest Unit Tests
In the next section, we’ll look at how to write unit tests for Jinja2 Transformations using the Infrahub test framework (located under tests/unit). These tests help ensure that a given input produces the expected output when rendered with the Jinja2 template.
.
├── baseline
│ ├── input.json
│ └── output.txt
├── no_server
│ ├── input.json
│ └── output.txt
└── test_arista_ntp.yml
2 directories, 5 files
---
version: "1.0"
infrahub_tests:
- resource: Jinja2Transform
resource_name: "arista_ntp"
tests:
- name: "baseline"
expect: PASS
spec:
kind: "jinja2-transform-unit-render"
directory: baseline
- name: "no_server"
expect: PASS
spec:
kind: "jinja2-transform-unit-render"
directory: no_server
{
"data": {
"InfraDevice": {
"edges": [
{
"node": {
"name": {
"value": "atl-core-2"
},
"location": {
"node": {
"name": {
"value": "Atlanta"
},
"servers": {
"edges": [
{
"node": {
"name": {
"value": "01.ntp.atl.acme.com"
}
}
},
{
"node": {
"name": {
"value": "02.ntp.atl.acme.com"
}
}
}
]
}
}
}
}
}
]
}
}
}
! Specify NTP servers
ntp server 01.ntp.atl.acme.com
ntp server 02.ntp.atl.acme.com
ntp enable
{
"data": {
"InfraDevice": {
"edges": [
{
"node": {
"name": {
"value": "atl-core-2"
},
"location": {
"node": {
"name": {
"value": "Atlanta"
},
"servers": {
"edges": [
]
}
}
}
}
}
]
}
}
}
! Specify NTP servers
ntp disable
pytest tests/unit
. This will execute the defined test cases and show whether the rendered output matches the expected result for each input scenario. 
Infrahub Proposed Changes
An Infrahub Proposed Change is similar to a pull request in Git. It allows users to review and discuss changes between branches. Reviewers can leave comments, request changes, and approve the proposed change before merging. This process ensures that changes are thoroughly vetted before integration into the main branch. Earlier in the post, we added the repository to Infrahub. With that in place, the typical workflow starts when you want to make a change. You would clone the repository, create a new branch, make your changes, and push that branch to your remote Git repository, whether that’s GitHub, GitLab, or another platform. Once the branch is pushed, Infrahub detects it and automatically creates a matching branch. At that point, you can head over to Infrahub to create a Proposed Change. As soon as the Proposed Change is created, Infrahub runs the defined tests for you. If there are any issues—for example, a syntax error in the query—the tests will fail, and you’ll see those results directly in the Infrahub interface. This helps catch problems early before merging anything into the main branch. To demonstrate this in action, I created a new branch called syntax_change, made a small edit by removing a closing parenthesis in the GraphQL file, committed the change, and pushed the branch to the remote repository.
git checkout -b syntax_update
git commit -m "updated syntax"
git push origin syntax_update


main
. 
