Should I test that?

Should I test helper functions?

Verdict

Yes

Test a helper function that holds its own logic with direct unit tests: call it with the main input and the edge inputs most likely to break, and assert on the return value.

Why

Test a helper function that holds its own logic with direct unit tests. In the typical case, a pure function that parses or formats a value for several callers, Blast radius is users and Change frequency is rarely, because a wrong result reaches customers through every caller and shared helpers change a few times a year. Detectability is eventually and Reversibility is with-effort, because caller tests pass only the inputs each caller needs, and callers store the plausible wrong value from an edge input until a script repairs it. Test cost is trivial, because a test imports the helper and calls it with no mock. Rule R11 gives Test.

When the decision changes
WhenDecisionWhy
The helper has one caller, and the tests of that caller reach every branch of the helper with data that gives each branch a different resultDo not test the helper directly; test the function that calls itDetectability moves to immediately and Reversibility to trivial, because the caller's tests fail when the helper breaks
The helper rounds amounts or converts currencies for invoicesTest mandatory, with rounding cases and boundary valuesBlast radius rises to money and Reversibility to costly
The helper checks whether a user belongs to the organisation that owns a recordTest mandatory: one allowed case and one denied caseBlast radius rises to safety-or-legal and Detectability to never
The helper reads the system clock or a fileTest minimally: one test of the main path with a fixed clock or a temporary fileTest cost rises to moderate, because each test must first control the clock or the file
The helper lives in test code and builds fixtures for other testsDo not test the fixture builder; the tests that use it fail when it breaksBlast radius falls to internal, Detectability to immediately and Reversibility to trivial
The helper is a custom assertion in test code, such as `assertSameInstant`Test: one test that shows the assertion fails for two different valuesBlast radius falls to internal, but Detectability moves to never, because a broken assertion lets every test that uses it pass

What breaks if you don't test

A bug in a shared helper reaches every caller at once, while caller tests stay green on the inputs that worked before. Suppose a change makes parse_duration("1h30m") return 60 minutes instead of 90, and the caller tests use whole hours only. Meeting rooms get booked for one hour, and support finds the pattern days after the release.

What you lose if you over-test

A direct test of a helper with one caller repeats the caller's tests. Merge the helper into its caller, and the direct test fails while users get the same result. Mocking a pure helper in caller tests hides its bugs instead: the mock keeps returning the old answer.

How to test

Import the helper and call it in unit tests, with no test doubles. The minimum is one test of the main path plus one per input likely to break: empty input, characters outside ASCII, and input the helper must reject. Keep inputs and expected outputs in one table with pytest parametrize or a parameterized test from the JUnit user guide. Keep one test of the main function too, because helper tests do not prove that the main function passes the right arguments.

When the answer changes

  • The helper has one caller whose tests reach all of its branches with data that tells the results apart.
  • The helper calculates an amount that customers pay, or decides who may read a record.
  • The helper is an assertion that other tests rely on to fail.

Real incident + Code example

The slug helper that dropped an alphabet

On a publishing platform I worked on, a slugify() helper turned article titles into URLs for the editor, the sitemap and the import job. A developer made it strip accents with Python's unicodedata.normalize("NFKD", ...) and an ASCII encode that ignored errors, so "Café" became "cafe". Cyrillic letters have no ASCII form, so every Ukrainian title became an empty string, which the helper's fallback turned into untitled. The caller tests used English titles and stayed green. For three weeks new Ukrainian articles shared one URL, until an editor found that her link opened someone else's story. A script rebuilt the slugs, and we added this test:

import pytest

from blog.text import slugify

@pytest.mark.parametrize("title, expected", [
    ("Hello World", "hello-world"),
    ("  Spaces around  ", "spaces-around"),
    ("Café au lait", "cafe-au-lait"),
    ("Привіт світ", "привіт-світ"),
    ("!!!", "untitled"),
])
def test_slugify(title, expected):
    assert slugify(title) == expected

The Ukrainian row fails against the ASCII version.

FAQ

Should we test helper functions?

Yes, test a helper function that holds its own logic by calling it directly and asserting on its return value. A helper that only forwards one value to another function needs no test of its own when the tests of its callers fail as soon as it breaks.

Do I need to test helper methods?

Test a public helper method that parses or calculates, because the tests of its callers seldom try its edge inputs. Test a private helper method through the public method that calls it.

Should I test helper functions or only the main function?

Test both when the main function's tests do not reach the helper's edge inputs: helper tests cover the inputs, and one main function test covers the wiring. Test only the main function when the helper has no other caller and the main function's tests reach every branch of the helper with data that gives each branch a different result.

Should I write tests for my test helpers?

Write a test for a custom assertion helper, because a broken assertion lets every test that uses it pass. Skip tests for fixture builders, since the tests that use them fail when they break.