TypeScript
Completed
-
Section 1: Getting Started
-
Section 2: Basic Types
-
Section 3: Functions and Objects
-
Section 4: Advanced Types
-
Section 5: Object-Oriented TypeScript
-
Section 6: Working with Modules
-
Section 7: Tooling and Practice
-
Section 8: Type-Level Programming
-
Section 9: TypeScript with Backends
-
Section 10: Testing Typed Code
-
Section 11: Data Validation with Types
-
Section 12: Practical Projects
-
Section 13: Compiler Internals
-
Section 14: Configuration Deep Dive
-
Section 15: Enums, Symbols, and Special Types
-
Section 16: Working with Async Code
-
Section 17: TypeScript and the DOM
-
Section 18: Advanced Generics Practice
-
Section 19: Working with Third-Party Types
-
Section 20: Monorepo and Large-Scale Practices
-
Section 21: Common Pitfalls and Best Practices
-
Section 22: Interview Practice
-
Section 23: Handbook: Narrowing In Depth
-
Section 24: Handbook: Object Types In Depth
-
Section 25: Handbook: Classes In Depth
-
Section 26: Handbook: Modules In Depth
-
Section 27: Handbook: Declaration Files In Depth
-
Section 28: JSX and Namespaces
-
Section 29: Compiler Configuration Reference
-
Section 30: More Practice Exercises
-
Section 31: Handbook: Everyday Types Deep Dive
-
Section 32: Utility Types Full Reference
-
Section 33: Decorators Reference
-
Section 34: Mixins and Advanced OOP Patterns
-
Section 35: Iterators and Generators Typing
-
Section 36: More Type-Level Programming Practice
-
Section 37: TypeScript Ecosystem Tools
-
Section 38: TypeScript with Testing Frameworks
-
Section 39: TypeScript for Library Authors
-
Section 40: More Interview Practice
-
Section 41: Handbook: Functions In Depth
-
Section 42: Handbook: Type Manipulation Deep Dive
-
Section 43: More Real-World Patterns
-
Section 44: TypeScript Release Notes Highlights
-
Section 45: Final Practice and Review
219: Testing Generic Functions
I'll be honest: when I first started with TypeScript, I thought testing generic functions was a bit of a waste of time. My logic was, "If the type system says it works for T, it works for everything, right?" Wrong. TypeScript's type safety disappears the moment your code is transpiled to JavaScript. If your generic logic has a bug—like assuming a value is always an object when it could be a string—the compiler won't always save you from a runtime crash.
To show you what I mean, let's build a small utility that wraps raw API data into a standardized Response object. This is a pattern I use in almost every project to keep error handling consistent.
Creating a generic API wrapper
First, we need a type that can wrap any data payload, and a function to perform the wrapping. I want this to be flexible enough to handle a single user, a list of products, or even just a simple success message.
type ApiResponse<T> = {
data: T;
timestamp: number;
status: 'success' | 'error';
};
function wrapResponse<T>(payload: T): ApiResponse<T> {
return {
data: payload,
timestamp: Date.now(),
status: 'success',
};
}
The logic here is trivial, but the contract is what matters. We're promising that whatever goes in as T comes out as the data property of our response.
The first pass: testing with a simple string
When testing generics, I usually start with the simplest possible type—usually a string or a number. It strips away the noise of object properties and lets me verify the basic wrapper logic.
test('wraps a string response correctly', () => {
const input = 'Operation completed';
const result = wrapResponse(input);
expect(result.data).toBe('Operation completed');
expect(result.status).toBe('success');
expect(typeof result.timestamp).toBe('number');
});
Wait, my test helper is losing type safety
Now, I don't like repeating myself. I decided to write a helper function to run these tests across different types. This is where I messed up. I wrote the helper like this:
// MISTAKE: Using 'any' inside the helper
function verifyWrapper(input: any) {
const result = wrapResponse(input);
expect(result.data).toEqual(input);
}
// I thought this was clever, but now I've lost all my TS benefits
verifyWrapper('Hello');
verifyWrapper({ id: 1 });
The problem? By using any in my helper, I've basically turned off TypeScript for my tests. If I accidentally changed wrapResponse to return a string instead of T, the helper wouldn't complain, and I'd only find out when the test actually failed at runtime. That defeats the purpose of using TypeScript for the tests in the first place.
I fixed this by making the test helper itself generic. It feels a bit more verbose, but it ensures that the input type and the resulting data type stay linked.
function verifyWrapper<T>(input: T) {
const result = wrapResponse(input);
// Now TS knows that result.data must be of type T
expect(result.data).toEqual(input);
}
Verifying complex shapes
The real test for a generic function is whether it handles complex objects without mutating them or losing properties. I'll add a test case for a User object to make sure the generics are preserving the structure perfectly.
interface User {
id: string;
email: string;
roles: string[];
}
test('preserves complex User object structure', () => {
const mockUser: User = {
id: 'u123',
email: 'dev@example.com',
roles: ['admin', 'editor']
};
const result = wrapResponse(mockUser);
// We can now access roles safely because of the generic T
expect(result.data.roles).toContain('admin');
expect(result.data.id).toBe('u123');
});
By testing both a primitive (string) and a complex object (User), we've confirmed that the generic T is behaving predictably regardless of the data shape. That's the goal: confidence that your abstraction doesn't break when the data gets messy.
📋 Practical Task
Build a Generic Pagination Validator
You are tasked with creating a pagination wrapper and a corresponding test suite. Follow these requirements:
- Create a generic type
PaginatedResponse<T>that contains:items: T[]totalCount: numbercurrentPage: number
- Write a function
paginate<T>(items: T[], page: number, pageSize: number)that returns aPaginatedResponse<T>. (For the sake of this exercise, the function can just return the full list of items and the requested page number). - Write a test suite that:
- Tests the function with an array of numbers.
- Tests the function with an array of objects (e.g.,
{ productName: string, price: number }). - Includes a generic test helper function that accepts an array of type
Tand verifies that theitemsproperty of the result matches the input array.
There are no comments for now.