# TypeScript's built-in utilities: the key to writing maintainable test code

A set of built-in type utilities in TypeScript lets you manipulate and operate with types differently.

## Common Type Utilities:

Common type utilities include:

* `Partial<T>`: Makes all properties of a type `T` optional.
    
* `Omit<T, K>`: Creates a new type that omits a specific set of properties `K` from the type `T`.
    
* `Exclude<T, U>`: Creates a new type that represents the set of values from `T` that are not assignable to `U`.
    
* `NonNullable<T>`: Creates a new type that represents the non-nullable version of `T`.
    
* `Record<K, T>`: Creates a new type that represents a record of values with keys of type `K` and values of type `T`.
    
* `Extract<T, U>`: Creates a new type that represents the intersection of `T` and `U`, i.e., the set of properties that are present in both `T` and `U`.
    
* `Pick<T, K>`: Creates a new type that consists of a subset of the properties of `T`, specified by the keys in the union type `K`.
    
* `Readonly<T>`: Makes all properties of a type `T` read-only.
    
* `Awaited<T>`: Utility is used to mark a type as "unwrapped" when used in an `await` expression.
    

These `type utilities` enable you to build adaptable and type-safe test suite components in various test automation settings.

## Utilities & its use cases:

Here are some instances where the built-in type utilities could be used in **API testing scenarios**:

### `Partial<T>`

This can be used to construct a type for a request body object with optional properties. When the API permits you to omit particular information from the request body, this can be handy.

For Example:

```typescript
interface CreateUserRequest {
  username: string;
  password: string;
  email?: string;
}

// Make all properties of CreateUserRequest optional
type PartialCreateUserRequest = Partial<CreateUserRequest>;

function createUser(request: PartialCreateUserRequest): Promise<void> {
  // Send request to API to create a new user
  return api.post('/users', request);
}

const request: PartialCreateUserRequest = {
  username: 'Srinivasan Sekar',
  password: 'mypass',
};

createUser(request);
// Sends request to API with only the 'username' and 'password' field
```

### `Omit<T, K>`

You can use `Omit<T, K>` to define a type for a response object that excludes specific fields. This is useful if you wish to omit specific fields from the return data that are irrelevant to your testing.

For Example:

```typescript
interface GetUserResponse {
  id: number;
  username: string;
  address: string;
  email: string;
}

// Omit the 'address' field from GetUserResponse
type OmitGetUserResponse = Omit<GetUserResponse, 'address'>;

function getUser(id: number): Promise<OmitGetUserResponse> {
  // Send request to API to get user data
  return api.get(`/users/${id}`);
}

getUser(1).then((response: OmitGetUserResponse) => {
  console.log(response);
  // { id: 1, username: 'srini', email: 'srini@example.com' }
});
```

### `Exclude<T, U>`

You can use `Exclude<T, U>` to create a type for a request parameter that is restricted to a specific set of values.

For Example:

```typescript
type OrderStatus = 'pending' | 'completed' | 'cancelled';

// Create a type for order status values that excludes 'cancelled'
type NonCancelledOrderStatus = Exclude<OrderStatus, 'cancelled'>;

function updateOrderStatus(orderId: number, status: NonCancelledOrderStatus): Promise<void> {
  // Send request to API to update order status
  return api.post(`/orders/${orderId}`, { status });
}

updateOrderStatus(1, 'pending');
// Sends request to API to set order status to 'pending'

updateOrderStatus(1, 'cancelled');
// This call would cause a type error because 'cancelled' is excluded from the allowed values for the 'status' parameter
```

In this case, the `NonCancelledOrderStatus` type provides a set of order status values that do not include the `cancelled` value. When you want to verify that the API does not accept requests to set the order status to `cancelled`, this can be handy.

### `NonNullable<T>`

This can be used to build a type for a response object that guarantees that specific fields are not null. When you want to verify that specific fields are always present in the return data, this can be handy.

```typescript
interface GetUserResponse {
  id: number | null;
  username: string | null;
  email: string | null;
}

// Create a type for the GetUserResponse that guarantees that the 'id' and 'username' fields are non-null
type NonNullableGetUserResponse = NonNullable<Pick<GetUserResponse, 'id' | 'username'>>;

function getUser(id: number): Promise<NonNullableGetUserResponse> {
  // Send request to API to get user data
  return api.get(`/users/${id}`);
}

getUser(10).then((response: NonNullableGetUserResponse) => {
  console.log(response);
  //  { id: 1, username: 'srini' }
});
```

In this example, the `NonNullableGetUserResponse` type represents a version of the `GetUserResponse` type where the `id` and `username` fields are guaranteed to be non-null. This can be useful when you want to ensure that these fields are always present in the response data and avoid null reference errors in your tests.

### `Record<K, T>`

This can be used to construct a type for a map of objects with a given set of properties. This can be handy when storing and retrieving data in a type-safe manner.

```typescript
interface User {
  id: number;
  username: string;
  email: string;
}

// Create a type for a map that maps user IDs to User objects
type UserMap = Record<number, User>;

const users: UserMap = {
  1: {
    id: 1,
    username: 'srini',
    email: 'srini@example.com',
  },
  2: {
    id: 2,
    username: 'sekar',
    email: 'sekar@example.com',
  },
};

function getUserById(id: number): User {
  return users[id];
}

const user = getUserById(2);
```

### `Extract<T, U>`

This can be used to build a type that represents the intersection of two object types. When you want to establish a type that represents a shared collection of properties between two objects.

```typescript
// Define the request and response types for the API
interface CreateUserRequest {
  name: string;
  email: string;
  password: string;
}

interface CreateUserResponse {
  id: number;
  name: string;
  email: string;
}

// Create a type for a function that sends a create user request and returns a create user response
type CreateUserClient = (request: CreateUserRequest) => Promise<CreateUserResponse>;

// Extract the common properties of CreateUserRequest and CreateUserResponse
type CreateUserClientRequestResponse = Extract<CreateUserRequest, CreateUserResponse>;

function logCreateUserRequestResponse(request: CreateUserClientRequestResponse): void {
  console.log(`Name: ${request.name}`);
  console.log(`Email: ${request.email}`);
}

const apiClient: CreateUserClient = (request) => {
  // Send request to API and return response
  return api.createUser(request);
};

apiClient({
  name: 'John Doe',
  email: 'john.doe@example.com',
  password: 'password123',
}).then((response: CreateUserResponse) => {
  logCreateUserRequestResponse({ ...request, ...response });
});
```

The `CreateUserClientRequestResponse` type represents the intersection of the `CreateUserRequest` and `CreateUserResponse` types, which include only the `name` and `email` properties that are present in both types. The `logCreateUserRequestResponse()` the function is modified to only log the `name` and `email` properties of the `CreateUserClientRequestResponse` object.

### `Pick<T, K>`

This utility in TypeScript creates a type that has a set of properties that are present in both `T` and `K`.

```typescript
interface User {
  id: number;
  name: string;
  email: string;
  password: string;
}

type UserPublicInfo = Pick<User, 'id' | 'name' | 'email'>;

function logUserPublicInfo(user: UserPublicInfo): void {
  console.log(`ID: ${user.id}`);
  console.log(`Name: ${user.name}`);
  console.log(`Email: ${user.email}`);
}

const user: User = {
  id: 123,
  name: 'Srini Sekar',
  email: 'Srini@example.com',
  password: 'password@123',
};

logUserPublicInfo(user);
```

In this example, the `UserPublicInfo` type represents a subtype of the `User` type that includes only the `id`, `name`, and `email` properties. The `logUserPublicInfo()` function is modified to only log these three properties of the `UserPublicInfo` object.

### `Awaited<T>`

The utility is used to mark a type as "unwrapped" when used in an `await` expression.

```typescript
async function getData(): Promise<Awaited<string>> {
  const data = await fetch('https://sample.com/data');
  return data;
}

const data = getData();
console.log(data);
```

### `Readonly<T>`

The `Readonly<T>` utility in TypeScript is used to create a read-only version of an object type. This means that the properties of the object cannot be modified.

```typescript
type ReadonlyString = Readonly<string>;

const str: ReadonlyString = 'Hello, world!';

// This line will cause a type error because str is read-only
str = 'Hello, world!';
```

In this example, the `ReadonlyString` type represents a read-only version of the `string` type. The code is not allowed to reassign the value of the `str` variable because it is marked as read-only.

Typescript-type utilities are extremely useful in API testing scenarios. They enable you to write test code that is reusable, flexible, and type-safe, making it easier to maintain and less prone to errors. Record, Extract, Pick, Readonly, and Awaited are among the most often used type utilities in API testing. You may construct more efficient and successful test automation suites by using these types of utilities.
