Library / SDK
total-typescript/shoehorn avatar
total-typescript/shoehorn

shoehorn: type safe partial mocks for TypeScript tests

Work seamlessly with partial mocks in TypeScript.

623 stars12 forksTypeScriptMIT

At a glance

What is it?
shoehorn is a small TypeScript library that replaces the as cast in tests with three named primitives, fromPartial, fromAny and fromExact, each with a different level of how much it will let you get away with. It is MIT licensed, built with tsup and tested with vitest.
Who is it for?
Adopt shoehorn if you write TypeScript tests that pass fixtures to functions expecting large objects and you are tired of as casts that defeat the compiler. Skip it if your codebase has deliberately narrow parameter types, since the library's own documentation says passing partial data to tests is usually a sign that the types are too loose.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 137 days ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The problem is the cast, not the fixture

shoehorn, which the README glosses as meaning to force something into a space, lets you pass partial data in tests while keeping TypeScript happy. The problem it names is stated in one line, that using as in tests feels bad, and the example shows why. A function takes a Request type whose body has an id, alongside oodles of other properties, and the test only cares about body.id, but the whole Request object still has to be passed, so the test reaches for a cast.

ts
type Request = {
  body: {
    id: string;
  };
  // Imagine oodles of other properties...
};

it("Should get the user", () => {
  // Even though we only care about body.id for
  // this test, we need to pass in the whole Request
  // object
  getUser({
    body: {
      id: "123",
    },
  } as Request);
});

Three costs of that cast are listed. You are trained not to use it, since a cast is a promise to the compiler that you are right and the compiler stops helping you. You need to manually specify the type you want to assert to, which means writing the type name twice and keeping the two in sync by hand. And for testing with incorrect data, you need to double-as, using as unknown as User, which is a cast of a cast. The last one is the worst, because deliberately wrong data is a normal thing to test, and the type system offers no sanctioned way to say so.

fromPartial keeps the check that matters

The solution the README proposes is to give you first-class primitives for safely providing incomplete data to tests, and the first of them is fromPartial.

ts
import { fromPartial } from "@total-typescript/shoehorn";

it("Should get the user", () => {
  getUser(
    fromPartial({
      body: {
        id: "123",
      },
    }),
  );
});

The function lets you pass a deep partial to a slot expecting a type, and the interesting part is that it does not simply turn off checking. It will fail if you pass something that does not match the expected type.

ts
// Type "1234123" has no properties in common
// with type 'PartialObjectDeep<Request>'
requiresRequest(fromPartial("1234123"));

So the value that comes out is typed as a full Request even though you only supplied part of it, and the compiler still rejects a value that has nothing in common with the expected shape. The error message quoted in the README even names the internal type, PartialObjectDeep, which tells you the implementation is a deep partial rather than a shallow one. That is the whole value proposition of the library in one line. You keep the type checking on the part of the object you did supply, and you get an escape from having to supply the rest, so the mistake that matters, a fixture with a misspelled field or a wrong value type, still fails at compile time.

fromAny and fromExact are the deliberate looser options

The other two primitives are variations on the same theme with different levels of strictness, and the README is careful to say what each one does. fromAny lets you pass anything to a slot while still giving you autocomplete on the original type.

ts
import { fromAny } from "@total-typescript/shoehorn";

requiresRequest(
  fromAny({
    body: {
      id: 124123,
    },
  }),
);

Here the id is a number where a string is expected, and fromAny does not complain. The README is blunt about the consequence, that it will not fail if you pass something that does not match.

ts
// All good!
requiresRequest(fromAny("1234123"));

That is the deliberate escape hatch, the type-safe equivalent of the double-as cast the library set out to replace, but named and located in one place rather than scattered through your test files. The third primitive goes the other way. fromExact is a convenience method for forcing you to pass all the properties of a type, and the README describes it as useful for when you want to swap in and out of fromPartial or fromAny.

ts
import { fromExact } from "@total-typescript/shoehorn";

requiresRequest(
  // Will fail! We're not passing all the oodles of
  // properties of Request
  fromExact({
    body: {
      id: 124123,
    },
  }),
);

So you have three levels of strictness available in the same import, and the design point is that you choose per call site rather than per file. A test that asserts on one field can use fromPartial, a test that deliberately passes junk can use fromAny, and a test that wants the full object can use fromExact, and the strictness is visible in the source rather than implied by a cast.

The library tells you when not to use it

The README contains a section asking whether passing partial data to tests is bad, and the answer is yes, in general. Having to pass huge objects to tests is a sign that your types are too loose, and ideally every function should only specify the data it needs. That is a specific technical claim rather than a general preference, and it is worth pausing on. A function that takes a whole Request when it only needs body.id has a parameter type that is wider than its actual dependency, and widening a parameter type is the same operation that makes code harder to change later, because every caller is now coupled to the shape of the whole object. Shoehorn makes that coupling invisible in the test but it does not remove it from the production signature. The README then says that unfortunately we live in the real world, and lists the two cases where the library is the best choice, a legacy codebase where you might not have the time to refactor everything to be perfect, and a third-party library whose types you cannot alter without writing needless wrapper functions. The honest reading is that shoehorn is a pressure valve, not a design recommendation, and a team that reaches for it in new code is usually working around a signature that should be narrowed.

How it is built and published

The package is small and the toolchain is visible in the manifest. It is published under the scope as @total-typescript/shoehorn, with a CommonJS main at dist/index.js, an ES module entry at dist/index.mjs and types at dist/index.d.ts, and the exports map points import at the module build and require at the CommonJS build. The build uses tsup with the cjs, esm and dts formats, the test script runs vitest, and the lint script is simply tsc, which is a nice touch because type checking as the lint step means the type errors are what the linter reports. The release script chains the whole thing.

bash
    "release": "turbo build lint test attw && changeset publish"

The attw step runs the arethetypeswrong CLI against the packed tarball, which checks that the type declarations are actually resolvable in every module system a consumer might use, a problem that is easy to ship and annoying to debug. Changesets handles the version bump and the changelog, and the .changeset/ directory at the top of the repository is where pending version changes are recorded as pull requests. The tooling is turbo for task orchestration, with a turbo.json, and the repository has a tests/ directory, a src/ directory, a tsconfig.json, a CHANGELOG.md and a pnpm-lock.yaml, so pnpm is the package manager. The dev dependencies are pinned to modest versions, with TypeScript at 4.9, vitest at 0.29, tsup at 7.1 and turbo at 1.8, and the author is Matt Pocock, whose totaltypescript.com site is the project's homepage.

The release history is thin, and the manifest says otherwise

There is a small inconsistency in the version numbers that is worth noting before you depend on the package. The release list shows v0.1.0 dated 2023-04-11 and v0.0.2 dated 2023-03-03, and there are no GitHub releases, so the tags on the repository are the record. The package.json, however, declares version 0.1.2, which is ahead of the newest tag by two patch releases. That means the published package on the registry is not exactly the same state as the newest tag, and if you need to reason about what changed between versions you have to read the CHANGELOG.md rather than the tags. The last push to the repository was on 2026-06-08 and the repository is not archived, so maintenance has not stopped, but the visible release activity is from 2023, which is consistent with a small library that reached a stable enough state and then needed little. The version is still below 1.0, so no one has made a compatibility promise, which for a three-function library is a reasonable position. The npm install instruction in the README is the whole installation story, and the package has no runtime dependencies at all, since the dependency list is entirely dev dependencies for the build and the tests. That is worth saying plainly, because a testing utility that pulls a tree of transitive dependencies into a project is a nuisance, and this one does not.

Editorial conclusion

Adopt shoehorn if you write TypeScript tests that pass fixtures to functions expecting large objects and you are tired of as casts that defeat the compiler. Skip it if your codebase has deliberately narrow parameter types, since the library's own documentation says passing partial data to tests is usually a sign that the types are too loose. Verify first that the primitive you choose matches your intent, because fromAny will accept a value of the wrong type without complaint while fromPartial will reject it, and check the published version, since the release list stops at v0.1.0 while the manifest declares 0.1.2.

Frequently asked questions

What does the shoehorn package do and how do I install it?

shoehorn lets you pass partial data in tests while keeping TypeScript happy, providing primitives for safely providing incomplete data. Install it with npm i @total-typescript/shoehorn, and the package has no runtime dependencies.

What is the difference between fromPartial, fromAny and fromExact?

fromPartial accepts a deep partial and fails if the value has no properties in common with the expected type, so the check survives. fromAny accepts anything while still offering autocomplete on the original type and will not fail on a mismatch. fromExact forces you to pass all the properties of the type and fails if you do not.

When should I not use partial mocks in TypeScript tests?

The README says that in general passing huge objects to tests is a sign that your types are too loose, and that ideally every function should only specify the data it needs. It points to legacy codebases and third-party libraries whose types you cannot change as the cases where this approach fits best.

Official sources

  1. License: MIT
  2. Project website
  3. README
  4. Releases
  5. total-typescript/shoehorn on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/total-typescript-shoehorn.svg)](https://hysenlabs.com/projects/total-typescript-shoehorn)

Was this useful?