Skip to content

Feature: Testing mocks for HTTPFetcher + FormParameter parsing - #32

Merged
germ-mark merged 13 commits into
mainfrom
feat/testing-mocks
Jul 29, 2026
Merged

Feature: Testing mocks for HTTPFetcher + FormParameter parsing#32
germ-mark merged 13 commits into
mainfrom
feat/testing-mocks

Conversation

@ThisIsMissEm

@ThisIsMissEm ThisIsMissEm commented May 31, 2026

Copy link
Copy Markdown
Contributor

In order to start writing tests for oauth4swift, we needed some test helpers. This had been on my list for a while, and well, today I happened to be being annoyed by my cough so needed something to focus on.

Major changes:

  • MockHTTPFetcher: allows mocking request by URL, and optionally method
  • FormParameters+Parsing: allows decoding a .data or .queryItems() value of FormParameters, intended for use with HTTP Testing.

Minor changes, currently in GermConvenienceTesting:

  • BundledHTTPRequest is now Equatable
  • HTTPDataResponse is extended with various methods for creating successful responses and responses with a given status code (and possible data)

As MockHTTPFetcher internally uses BundledHTTPRequest for storage, so we do actually capture the request body, so we can make an assertion about the request that we send: url, headers, body, everything.

This pull request was authored with the help of Claude, however, I largely wrote the code and claude reviewed and guided me when I got stuck with swift specifics, rather than it implementing the code (I only had it implement a few minor changes)

@changeset-bot

changeset-bot Bot commented May 31, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 1890efb

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@germ-network/germ-convenience Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@ThisIsMissEm
ThisIsMissEm requested a review from germ-mark May 31, 2026 02:21
New GermConvenienceTesting library target with:
  - FormParameters+Parsing: init(parsing:) extensions for Data, URLComponents, and [URLQueryItem]

This helps with evaluating whether requests we send are the correct shape.
Introduces MockHTTPFetcher which conforms to HTTPFetcher allowing easier writing of test assertions:
  - MockHTTPFetcher: method-aware HTTP mock with per-URL handler queues, exact-to-any fallback, and request logging
  - HTTPResponseError: Equatable conformance to support error matching in tests

@germ-mark germ-mark left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd suggest you adopt the three target structure of "library, mocks, tests", e.g. in
https://github.com/germ-network/AtprotoTypes

Where the mocks live in a separate target, can be adopted by downstream Mock and Test Targets. The tests can then import the library and the mock.

(I have been meaning to and this would be a good time to lift the Mockable target from AtprotoTypes to GermConvenience. don't need to do it as part of this PR, but now is a good time in this next release)

let method: MethodMatcher
}

private let lock = NSLock()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is the function of the lock here?

Comment thread Sources/GermConvenienceTesting/HTTPTypes/MockHTTPFetcher.swift Outdated
Comment thread Sources/GermConvenienceMocks/HTTPTypes/MockHTTPFetcher.swift Outdated
Comment thread Sources/GermConvenienceMocks/FormParameters+Parsing.swift Outdated
germ-mark and others added 4 commits July 28, 2026 23:16
on() parked the url and method on the actor for enqueue() to read, so the pair
was not atomic: two chains configuring one fetcher concurrently could enqueue a
response against each other's url. Measured 24% misattribution with two tasks
under load and ~97% with eight. on() now returns a Registration carrying the
url and method, so the race is unrepresentable rather than unlikely, and
Errors.missingOnUrl is gone with it.

A drained exact-method queue with no .any handler to fall back to reported
unmockedRequest, which reads as "never mocked" - the exact-to-any fallback made
tooManyRequests structurally unreachable for exact keys. Registered-but-drained
now reports tooManyRequests for either key. The intentional fallback to .any is
unchanged.

Requests that matched no handler are now logged too, matching the tooManyRequests
case, so requests(for:) is a complete record of what the code under test sent.

Registration urls go through the same round trip lookup uses, so on(origin)
matches rather than keying a url no request can produce.

Also picks up the two implicit returns from review, and corrects the changeset,
which still named the target GermConvenienceTesting after the rename to
GermConvenienceMocks.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Make MockHTTPFetcher registration atomic and fix drained-queue reporting
The copy here would not have failed the build - it compiles with only
"conformance was already stated in the type's module", and the owner's witness
silently wins - so the mocks' == would have been shadowed rather than rejected.

Also covers the .head rejection this branch added, which had no test, and the
matching case where a bodiless method carries no body.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@germ-mark
germ-mark merged commit 9f7e232 into main Jul 29, 2026
10 checks passed
@germ-mark
germ-mark deleted the feat/testing-mocks branch July 29, 2026 06:43
@github-actions github-actions Bot mentioned this pull request Jul 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants