Testing — Part 6: Writing Tests for DictionaryAdapter
Ticket: SCRUM-75 | Status: In Progress
What This Task Was About
Write unit tests for DictionaryAdapter — the service that calls an external dictionary API using fetch to look up word definitions.
Functions to test:
lookup(word)— should return a Word object for a valid wordlookup(word)— should throw an error for an invalid word (404)lookup(word)— should handle network errors gracefully
Key difference from previous tests: DictionaryAdapter uses the global fetch — so we mock global.fetch directly with vi.fn() instead of mocking a Firebase module.
Mocking global.fetch
DictionaryAdapter calls the real internet dictionary API. In tests, we replace global.fetch with a fake function that returns what we tell it to — no network needed.
Mock a successful response
global.fetch = vi.fn(() =>
Promise.resolve({
ok: true,
json: () => Promise.resolve([{
word: 'ephemeral',
meanings: [
{
partOfSpeech: 'adjective',
definitions: [{ definition: 'lasting for a very short time' }],
},
],
}]),
})
) as unknown as typeof fetch;
Mock a failed response (word not found)
global.fetch = vi.fn(() =>
Promise.resolve({
ok: false,
status: 404,
})
) as unknown as typeof fetch;
Mock a network error
global.fetch = vi.fn(() =>
Promise.reject(new Error('Network error'))
) as unknown as typeof fetch;
Test Structure
import { DictionaryAdapter } from '../DictionaryAdapter';
describe('DictionaryAdapter', () => {
describe('lookup', () => {
it('should return a Word object for a valid word', async () => {
// Arrange
global.fetch = vi.fn(() =>
Promise.resolve({
ok: true,
json: () => Promise.resolve([{
word: 'ephemeral',
meanings: [{
partOfSpeech: 'adjective',
definitions: [{ definition: 'lasting for a very short time' }],
}],
}]),
})
) as unknown as typeof fetch;
// Act
const result = await DictionaryAdapter.lookup('ephemeral');
// Assert
expect(result).not.toBeNull();
expect(result.name).toBe('ephemeral');
});
it('should throw an error for an invalid word', async () => {
global.fetch = vi.fn(() =>
Promise.resolve({ ok: false, status: 404 })
) as unknown as typeof fetch;
await expect(DictionaryAdapter.lookup('xyznotaword')).rejects.toThrow();
});
it('should handle network errors gracefully', async () => {
global.fetch = vi.fn(() =>
Promise.reject(new Error('Network error'))
) as unknown as typeof fetch;
await expect(DictionaryAdapter.lookup('ephemeral')).rejects.toThrow('Network error');
});
});
});
Why as unknown as typeof fetch
TypeScript knows the type signature of global.fetch is very specific. A vi.fn() mock doesn't fully match that type — it returns a simplified object, not a full Response. The double cast as unknown as typeof fetch tells TypeScript: "trust me, treat this as a real fetch for the purposes of this test."
This pattern is common when mocking browser globals in TypeScript tests.
Key Takeaways
- Mock
global.fetchdirectly withvi.fn()when your code uses the native fetch API. - Mock the shape your code actually reads — if your code calls
response.json(), the mock must include ajsonmethod. as unknown as typeof fetchis the standard TypeScript cast for mocking browser globals.- Testing error cases (404, network error) is just as important as the happy path.