TypeScript utility types for test automation: Partial and Omit

TypeScript comes with a variety of built-in utility types. Two of them - Partial and Omit - can be particularly useful in API tests.

TypeScript logo

Partial

Our backend services, web and mobile clients, and tests generate code from OpenAPI specifications with openapi-generator. This approach has numerous advantages, I described them a couple of years ago. To put it shortly, code generation saves time, reduces the risk of human error, and ensures consistency.

These models follow a specification, which often marks some path, query, or body parameters as required. The same notion exists in TypeScript:

type PostArticleRequest = {
  title: string; // can't be undefined
  summary: string; // can't be undefined
  text?: string; // can be undefined
  tags?: string[]; // can be undefined
};

To create an article, a client must send a title and a summary, while the other two properties are optional. Tests can build PostArticleRequest objects and pass them to the API layer:

async postArticle(body: PostArticleRequest): Promise<PostArticleResponse> {
    // make a request with this body
}

But what if we wanted to validate that the server returns an error when a mandatory parameter is missing? The generated model prohibits that.

To make our API client more flexible, we can use Partial. It takes T and constructs a new type where all properties of T are optional.

type PartialPostArticleRequest = Partial<PostArticleRequest>;

/***
 * Same as:
 *
 * type PartialPostArticleRequest = {
 *   title?: string | undefined;
 *   summary?: string | undefined;
 *   text?: string | undefined;
 *   tags?: string[] | undefined;
 * }
 ***/

So if the API layer accepts Partial<PostArticleRequest> instead of plain PostArticleRequest, we are free to choose what parameters to send:

async postArticle(body: Partial<PostArticleRequest>): Promise<PostArticleResponse> {
    // make a request with this body
}

await postArticle({
  // don't set title
  // add other props
})

This type is also useful for overrides in test data factories.

const requestFactory = {
  postArticle(overrides?: Partial<PostArticleRequest>): PostArticleRequest {
    return {
      title: "random title",
      summary: "random summary",
      text: "random text",
      tags: ["tag1", "tag2"],
      ...overrides,
    };
  },
};

// we don't need to pass summary if we only want to modify text
requestFactory.postArticle({ text: "modified text" });

Official documentation: https://www.typescriptlang.org/docs/handbook/utility-types.html#partialtype

Omit

Some requests can contain loads of properties, necessitating just as many nearly identical tests. Omit<T, K> can offer another solution! It takes T and constructs a new type, removing all K keys.

type OmitPostArticleRequest = Omit<PostArticleRequest, "title" | "summary">;

/***
 * Same as:
 *
 * type OmitPostArticleRequest = {
 *   text?: string | undefined;
 *   tags?: string[] | undefined;
 * }
 ***/

In a test, we can define an array of keys to exclude and make individual API calls without them:

const testData: PostArticleRequest = {
  title: "random title",
  summary: "random summary",
  text: "random text",
  tags: ["tag1", "tag2"],
};

const keys = ["title", "summary"];

const requestBody = (key: string) => {
  const { [key as keyof PostArticleRequest]: _, ...restProps } = testData;

  return restProps;
};

for (const key of keys) {
  console.log(`Request body without ${key.toUpperCase()}:`, requestBody(key));

  // fetch
  // assert status code = 400
}

Output:

aleksei@debian:Blog
 └── bun post.ts
Request body without TITLE: {
  summary: "random summary",
  text: "random text",
  tags: [ "tag1", "tag2" ],
}
Request body without SUMMARY: {
  title: "random title",
  text: "random text",
  tags: [ "tag1", "tag2" ],
}

Official documentation: https://www.typescriptlang.org/docs/handbook/utility-types.html#omittype-keys