You might want Pick not Partial in TypeScript
typescriptPartial
is a TypeScript utility-type that transforms all the property value types of an object to be optional. This can be useful when writing tests that only look at certain parts of an object. However, for TypeScript to be happy with passing said semi-populated object to a function, you must use the as
keyword to force TypeScript to see it as the full object. This is not ideal as you lose a degree of type safety in that your function thinks it has a full object but in fact does not. So, if you update your function to use other properties of the object, you may start getting runtime errors that could have been caught by TypeScript.
One solution I have been experimenting with is moving the process of scoping object parameters. So rather than using Partial
on the caller side, we can use Pick
— a TypeScript utility-type that reduces an object type to given properties — on the implementation side. Meaning we are telling TypeScript exactly what properties of a perhaps more complex object we intend to use.
If you have ever written Express middleware or similar, you may have come across this issue recreating req and res. So, if we are trying to test this middleware we only need to create a req and/or res object with the properties we need. Let's look at an example:
typescript
// @filename: authenticate.tsimport {NextFunction ,Request ,Response } from "express";conststaticKey = "f9asdjb28asfdlmx";export typeAuthRequest =Pick <Request , "params" | "headers">;export typeAuthResponse =Pick <Response , "send" | "sendStatus">;export default async functionauthenticate (req :AuthRequest ,res :AuthResponse ,next :NextFunction ) {if (req .headers .authorization ?.replace ("Bearer: ", "") ===staticKey ) {const {name = "user" } =req .params ;res .send (`Hello ${name }`);} else {res .sendStatus (403);}}
The beauty of using Pick
above is that we can still pass it a complete req/res object for example when passing it to app.get
or app.use
and TypeScript won't complain. Now we can look at the advantage of this approach: we can construct objects with just the properties we need for each test:
typescript
// @filename: authenticate.test.tsimportauthenticate , {AuthResponse } from "./authenticate";test ("unauthorized user", async () => {constres :AuthResponse = {send :jest .fn (),sendStatus :jest .fn (),};awaitauthenticate ({params : {},headers : {},},res ,jest .fn ());expect (res .sendStatus ).toHaveBeenCalledTimes (1);expect (res .sendStatus ).toHaveBeenCalledWith (403);});
You could apply this to more than just testing. Anywhere you are accepting an object but only using a subset of properties could perhaps benefit from this approach. As with most things, it all depends on context, but it is worth keeping in mind that Pick
might be a better option than Partial
.