How I Use Git Bisect to Find Where My Test Broke

In this post, I’ll talk about how I used the Git bisect tool to find the specific commit that caused my test to fail.

Let’s say I create a simple project with an entry point that says, “Welcome to this app!” I then write a test asserting that the entry page says, “Welcome to this app!” My app is running and my one test is passing. Great!

I want to add a few things, so I commit a bunch of little modifications to my main page.

After a while, I run the test suite, and I see that my one test is no longer passing. My test was passing initially but, somewhere along the way, it stopped passing. I want to find out exactly when my test started failing.

 

Examining My Commit History

I know the test was passing in my initial commit but is not passing now.

My commit history looks like this:


commit 0fc90a0876ca253f01624304b274b7243a

add banana pie text

❌ test not passing


commit fb62b20dd48c0e668fb12d02b04bf09290

add banana bread text

❓ test maybe passing


commit 3c0aeb0cc03910c9c51c46638e2c72fc50

remove welcome text and add other text

❓ test maybe passing


commit e05af554acb68ad52e4bd9a7711f92edf0

modify text after header text

❓ test maybe passing


commit a5b79ec15c653676053292420d4ed29b9c

add text under the welcome text 2

❓ test maybe passing


commit 1322afcc38c06945d0501ec81c9ec791ed

add text under the welcome text

❓ test maybe passing


commit 4b10e5bef83965e977e02ff2a88c3b58ab

building out main page


✅ test passing

I know my test was passing at the building out main page commit and but it’s failing now. I want to find the commit where the test went from passing to failing. Assuming that checking CI is not an option, I could start with the building out main page commit and individually check out each commit to identify where the stopped passing. That would work okay for my little example project with a few commits but would quickly become untenable if this was a large project with many contributors.

Using Git Bisect to Find the Commit That Caused the Failure

Instead, I’ll reach for a handy tool: git bisect. I reach for git bisect whenever I want to identify the exact commit that introduced something. I often use it to identify which commit caused degradation in performance or introduced a bug. However, this tool could also easily be used to identify a commit responsible for speeding up the performance or fixing a styling issue.

In this example, I want to identify which commit caused my test asserting that I would see “Welcome to this app!” on the home page to start failing. I know my test was passing on the building out main page (4b10e5bef83965e977e02ff2a88c3b58ab) commit but is now failing at the most recent commit add banana pie text(0fc90a0876ca253f01624304b274b7243a). I need to identify where between those two commits the test went from passing to failing. Luckily, the “good” commit (where the test was passing) and the “bad” commit (where the test was failing) are the only things I need to start using git bisect.

I’ll start a git bisect with:

 git bisect start 

Next, I’ll provide the “good” commit and the “bad” commit:

 
git bisect bad # current commit is bad

git bisect good 4b10e5bef83965e977e02ff2a88c3b58ab # "building out main page" commit is good

Then, git bisect will find the commit between the “good” commit and the “bad” commit and check that commit out. Then, it’s up to me to decide if this newly checked-out commit is a “good” commit or a “bad” commit. In this example, the commit is “good” if my one test passes and “bad” if the test fails. I run the main page test and it passes, so I’ll mark the currently checked out commit as “good”:

 
git bisect good 

I’ve allowed git bisect to narrow the possible commits down, but I’m not quite finished yet. There is still a small range of commits between the “good” commit and the “bad” commit. Again, git bisect will pick a commit in the middle and I’ll have to determine the status of the commit by running my test. This time, the test fails, so I’ll mark the currently checked out commit as “bad”:

 
git bisect bad 

The bisect has narrowed down the commits enough that it is able to print out the offending commit:

Bisecting: 0 revisions left to test after this (roughly 0 steps)
[3c0aeb0cc03910c9c51c46638e2c72fc50a958c0] remove welcome text and add other text

I now can see that my “remove welcome text and add other text” commit broke my test. That makes a lot of sense!

An Even Faster Way to Use Git Bisect

What if I had way more commits and I didn’t want to have to individually go through and specify if a commit is “good” or “bad”?

Instead, I could provide the git bisect with a program or script that determines the status of a given commit. If the script exits successfully for the commit, the commit will be marked as “good.” Otherwise, on failure, the commit will be marked as “bad.” The script enables the git bisect to determine where the status went from “good” to “bad.”

In my case, the status is determined by the success of my test, so the script I provide to git bisect would be running the test:


git bisect yarn test

The result?


commit 3c0aeb0cc03910c9c51c46638e2c72fc50a958c0

    remove welcome text and add other text

The script definitely made the process a little faster. What an easy way to find where I broke the test!

Now that I know where the problem is, it’s time to fix it.