This guide explains how to test the API and understand the unit tests.
Test the live API:
# Get SOL tokens
curl https://e-back-ca4c.onrender.com/api/tokens?q=SOL
# Get DOGE tokens
curl https://e-back-ca4c.onrender.com/api/tokens?q=DOGE
# Default query (SOL)
curl https://e-back-ca4c.onrender.com/api/tokensJust visit these URLs:
- https://e-back-ca4c.onrender.com/api/tokens?q=SOL
- https://e-back-ca4c.onrender.com/api/tokens?q=PEPE
- Import
postman_collection.json - Update the
base_urlvariable to:https://e-back-ca4c.onrender.com - Run the requests
fetch('https://e-back-ca4c.onrender.com/api/tokens?q=SOL')
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));Unit tests verify that individual pieces of code (functions, classes) work correctly in isolation.
Benefits:
- β Catch bugs early
- β Document how code should behave
- β Make refactoring safer
- β Improve code quality
Every test follows this pattern:
describe('What you're testing', () => {
it('should do something specific', () => {
// Arrange: Set up test data
const input = 'SOL';
// Act: Call the function
const result = someFunction(input);
// Assert: Check the result
expect(result).toBe(expected);
});
});What it tests:
- β Caching works correctly
- β Data merging from multiple APIs
- β Deduplication logic
- β Edge cases (empty data, errors)
Example Test:
it('should return cached data if available', async () => {
// Mock: Pretend cache has data
mockCacheService.get.mockResolvedValue(cachedData);
// Call the function
const result = await aggregatorService.getAggregatedData('SOL');
// Verify: Should return cached data without calling APIs
expect(result).toEqual(mockData);
expect(mockFetcherService.fetchDexScreenerData).not.toHaveBeenCalled();
});Why this test matters:
- Ensures caching works (saves API calls)
- Verifies we don't waste time fetching when data exists
What it tests:
- β HTTP endpoints return correct status codes
- β Query parameters work
- β Error handling
- β Response format
Example Test:
it('should return 200 and token data', async () => {
// Mock the service
mockAggregatorService.getAggregatedData.mockResolvedValue(mockTokens);
// Make HTTP request
const response = await request(app)
.get('/api/tokens')
.query({ q: 'SOL' });
// Verify response
expect(response.status).toBe(200);
expect(response.body).toEqual(mockTokens);
});Why this test matters:
- Ensures API works as expected
- Catches breaking changes to endpoints
What: Replace real dependencies with fake ones
Why: Test in isolation without external dependencies
// Mock the cache service
jest.mock('../services/cache');
// Now we control what it returns
mockCacheService.get.mockResolvedValue('fake data');What: Check if results match expectations
expect(result).toBe(5); // Exact match
expect(result).toEqual(object); // Deep equality
expect(result).toHaveLength(3); // Array/string length
expect(fn).toHaveBeenCalled(); // Function was called
expect(fn).toHaveBeenCalledWith(x); // Called with specific argsWhat: Test asynchronous code (promises, async/await)
it('should fetch data', async () => {
const result = await fetchData(); // Wait for promise
expect(result).toBeDefined();
});npm testOutput:
Test Suites: 2 passed, 2 total
Tests: 12 passed, 12 total
Time: 11.254 s
npm run test:watchWhat it does:
- Watches for file changes
- Re-runs tests automatically
- Great for development!
npm run test:coverageWhat it shows:
- % of code covered by tests
- Which lines aren't tested
- Helps identify gaps
Example Output:
File | % Stmts | % Branch | % Funcs | % Lines
--------------------|---------|----------|---------|--------
aggregator.ts | 95.2 | 88.9 | 100.0 | 95.2
api.ts | 90.0 | 75.0 | 100.0 | 90.0
β Aggregator Service (6 tests)
- Returns cached data when available
- Fetches from APIs on cache miss
- Merges data from multiple sources correctly
- Deduplicates tokens by address
- Caches merged results
- Handles empty API responses
β API Endpoints (6 tests)
- Returns 200 with token data
- Uses default query when missing
- Returns empty array for no results
- Handles errors gracefully (500 status)
- Accepts different query parameters
- Returns JSON content type
- β Empty API responses
- β Cache hits and misses
- β Duplicate tokens
- β Missing query parameters
- β API errors
- β Different query strings
Ask yourself:
- What should this function do?
- What inputs will it receive?
- What should it return?
- What can go wrong?
describe('MyFunction', () => {
it('should return sum of two numbers', () => {
const result = add(2, 3);
expect(result).toBe(5);
});
it('should handle negative numbers', () => {
const result = add(-2, 3);
expect(result).toBe(1);
});
});npm testIf it fails, fix your code or test until it passes!
// BAD: Uses real Redis
const result = await aggregatorService.getAggregatedData('SOL');// GOOD: Mocks Redis
jest.mock('../services/cache');
mockCacheService.get.mockResolvedValue(null);// BAD: Only tests happy path
it('should return data', async () => {
const result = await fetchData('SOL');
expect(result).toBeDefined();
});// GOOD: Tests edge cases too
it('should handle empty query', async () => {
const result = await fetchData('');
expect(result).toEqual([]);
});// BAD: Tests affect each other
let data = [];
it('test 1', () => { data.push(1); });
it('test 2', () => { expect(data).toEqual([1]); }); // Fails!// GOOD: Clean state for each test
beforeEach(() => {
data = [];
});Before submitting:
- All tests pass (
npm test) - Coverage > 70% (
npm run test:coverage) - Tests cover happy path
- Tests cover edge cases
- Tests cover error handling
- No console errors in test output
You now have 12 passing tests covering:
- β Service logic
- β API endpoints
- β Caching
- β Data merging
- β Error handling
- β Edge cases
Great job! π