Skip to content

Multiple always_expect() on one mock are silently discarded — misleading message, and no argument-routed persistent expectations #73

Description

@thoni56

Summary

Declaring more than one always_expect() for the same mocked function keeps only the first; each later one is discarded with a message that's easy to misread. This also surfaces a feature gap: there's no way to have several persistent expectations routed by their when() matchers.

Observed behaviour

Two always_expects on one function, differing only in when():

always_expect(listFilesInDirectory, when(dirPath, is_equal_to_string("/a")), will_return(listA));
always_expect(listFilesInDirectory, when(dirPath, is_equal_to_string("/b")), will_return(listB));

The second emits, at its declaration line:

Mocked function [listFilesInDirectory] already has an expectation and will always be called a certain way; any expectations declared after an always expectation are discarded

Only the first always-expectation stays live.

Why the wording misleads

"will always be called a certain way" reads as a claim about the constraints (that they must match), rather than the actual rule: only one always expectation per function; later ones are dropped. It also names no workaround. Something like:

▎ "function 'X' already has an 'always' expectation; cgreen allows only one per function,
▎ so this one is discarded — use sequential expect() for differing per-call results"

would point straight at the fix.

Underlying feature gap

The triggering use was a recursive directory walk calling listFilesInDirectory(dir) an arbitrary number of times across arbitrary dirs — wanting "persistent, return routed by the dir argument." Neither FIFO expect() (order-coupled) nor a single always_expect() fits.

gmock-style argument-matched repeats (EXPECT_CALL(m, f(Eq(x))).WillRepeatedly(...)) would cover it.

Open question (not verified against source)

Does a single always_expect() with a when() actually verify its constraint per call? We only observed the "second-and-later discarded" behaviour, not this. A 3-case check would settle it: single-always satisfied / single-always violated / two routed by when.

Context

Surfaced from real usage (c-xrefactory) mocking a recursive filesystem walk; filed at suspicion level. (Drafted via Claude Code.)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions