← All posts
Aug 11, 2026 · 4 min read

Bazel reverse dependency testing for monorepos

BazelMonorepoTesting

One of the nicest things about Bazel is that it already knows the shape of your dependency graph. That means we can ask it a very useful question:

If I change package B, which other packages depend on it and should be tested again?

This matters a lot in a monorepo.

Imagine a very small example:

Now if I change something in package B, it is not enough to only test B.

If B breaks in a subtle way, A may fail immediately, C may still build, and D may fail much later in a different codepath. That is exactly the kind of cascading failure we want to catch before it escapes.

In this post I will show a simple way to make Bazel help us with that.


The basic idea

Instead of running every single test in the repository every time, we can run tests for the impacted targets only.

That means:

  1. Find the package that changed
  2. Ask Bazel for the reverse dependencies of that package
  3. Run the tests for those impacted packages

This gives us a nice balance:


A tiny example

Let us assume we have three packages in a monorepo:

Where both A and C depend on B.

If we want Bazel to tell us who is affected by B, we can use rdeps.

bazel query 'rdeps(//..., //packages/b)'

The query above asks Bazel to return all reverse dependencies of //packages/b within the workspace.

That is the key piece.

If B changes, Bazel can tell us that A and C are affected. If C itself has more downstream dependents, those will show up too.


What rdeps gives us

rdeps stands for reverse dependencies.

If target X depends on target Y, then X is a reverse dependency of Y.

So for a shared module or library, reverse dependency analysis is exactly what we want for safe monorepo testing.

I find this extremely useful because it shifts the responsibility from humans remembering which packages might be impacted, to Bazel actually computing it for us.

That is a much better deal.


Turning that into a test command

In practice, we usually do not want to manually type a query every time.

Instead, we can wrap it in a small script that:

  1. accepts the changed target as input
  2. resolves the reverse dependencies
  3. runs bazel test on the result

Something like this is enough to get started:

#!/usr/bin/env bash
set -euo pipefail

TARGET="${1:-}"

if [[ -z "$TARGET" ]]; then
  echo "Usage: $0 //path/to:target"
  exit 1
fi

TEST_TARGETS=$(bazel query "rdeps(//..., ${TARGET})" --output=label)

if [[ -z "$TEST_TARGETS" ]]; then
  echo "No reverse dependencies found for ${TARGET}"
  exit 0
fi

bazel test ${TEST_TARGETS}

This is intentionally simple.

If package B changes, we can run:

./scripts/test_affected.sh //packages/b

And Bazel will test everything that depends on it.


Why this is better than just testing B

At first glance, it may feel enough to run tests inside the package you changed.

That works for local correctness, but it does not tell the whole story.

If B exposes a function that changes behavior slightly, B’s own tests might still pass. But A may have a hidden assumption about that behavior, and C may rely on a different edge case.

That is why reverse dependency testing is so useful.

It does not just validate the package you touched.

It validates the ecosystem around it.


A more realistic workflow

What I usually like in a monorepo is something like this:

  1. developer changes a shared package
  2. CI detects the changed targets
  3. CI asks Bazel for reverse dependencies
  4. CI runs tests only for impacted targets

That keeps the feedback loop tight without having to brute-force the whole repo every time.

You can also combine this with presubmit checks so that any change to a shared package automatically expands the test surface.

That is often the sweet spot between speed and safety.


A note on build vs test

One thing I want to call out is that build success is not the same as behavioral safety.

bazel build will tell you that the code still compiles and links correctly.

But bazel test is what tells you that the dependent package still behaves correctly.

For shared libraries especially, I would strongly prefer to run tests on the reverse dependency graph rather than just a build.

That extra step catches the kinds of mistakes that usually slip through review.


Practical takeaway

If you are working in a Bazel monorepo and you want to reduce accidental breakage, the pattern is pretty straightforward:

That way, when package B changes, packages A and C do not get silently forgotten.

They are tested automatically, and that is exactly what we want.

If you want, in a follow-up post I can show a slightly more production-ready version of this setup with a real CI script and some filtering so it only tests the affected Go targets.