simple-news.test.ts•6.23 kB
/**
* Super simplified controller tests with minimal dependencies
*
* This file demonstrates best practices for testing controllers:
* - Proper TypeScript type handling
* - Consistent setup/teardown
* - Focused unit testing of controller logic
* - Simple, maintainable mocking approach
*/
import { describe, test, expect, jest, beforeEach, afterEach } from '@jest/globals';
import { Request, Response } from 'express';
// Define types for our mocks to avoid TypeScript errors
type MockResponse = {
status: jest.Mock;
json: jest.Mock;
};
// Create mock implementations that we'll use throughout the tests
const mockGetTopNews = jest.fn();
const mockGetNewsByUuid = jest.fn();
// Mock the entire news service
jest.mock('../../services/news.service', () => {
return {
NewsService: jest.fn().mockImplementation(() => ({
getTopNews: mockGetTopNews,
getNewsByUuid: mockGetNewsByUuid
}))
};
});
// Mock the cache service to avoid side effects
jest.mock('../../utils/cache', () => ({
cacheService: {
get: jest.fn(),
set: jest.fn(),
clear: jest.fn(),
getStats: jest.fn().mockReturnValue({ hits: 0, misses: 0, keys: 0 })
}
}));
// Import the controller after mocking
import { NewsController } from '../../controllers/news.controller';
describe('NewsController - Minimal Tests', () => {
// Test subjects
let controller: NewsController;
let req: Partial<Request>;
let res: MockResponse;
// Setup before each test
beforeEach(() => {
// Reset all mocks
jest.clearAllMocks();
mockGetTopNews.mockReset();
mockGetNewsByUuid.mockReset();
// Create a fresh controller for each test
controller = new NewsController();
// Setup basic mock request with all required query parameters
req = {
query: {
// Initialize all query parameters expected by the NewsController.getTopNews method
search: undefined,
search_fields: undefined,
locale: undefined,
categories: undefined,
exclude_categories: undefined,
domains: undefined,
exclude_domains: undefined,
source_ids: undefined,
exclude_source_ids: undefined,
language: undefined,
published_before: undefined,
published_after: undefined,
limit: undefined,
page: undefined,
sort: undefined
},
params: {}
};
// Setup response with properly typed mocks
res = {
status: jest.fn().mockReturnThis(),
json: jest.fn()
} as unknown as MockResponse;
});
// Clean up after each test
afterEach(() => {
jest.clearAllMocks();
});
describe('getTopNews', () => {
test('returns successful response with 200 status', async () => {
// Arrange - Set up mock to return successful response
const mockData = {
data: [{ title: 'Test Article', uuid: 'test-123' }],
meta: { found: 1, returned: 1 }
};
mockGetTopNews.mockResolvedValue(mockData);
// Act - Call the controller method
await controller.getTopNews(req as Request, res as unknown as Response);
// Assert - Verify correct response
expect(mockGetTopNews).toHaveBeenCalled();
expect(res.status).toHaveBeenCalledWith(200);
expect(res.json).toHaveBeenCalledWith(expect.objectContaining({
success: true,
data: expect.any(Array)
}));
});
test('handles API errors with 500 status', async () => {
// Arrange - Force service error
mockGetTopNews.mockRejectedValue(new Error('API error'));
// Act - Call the controller method
await controller.getTopNews(req as Request, res as unknown as Response);
// Assert - Verify error handling
expect(res.status).toHaveBeenCalledWith(500);
expect(res.json).toHaveBeenCalledWith(expect.objectContaining({
success: false,
error: expect.any(String)
}));
});
test('passes query parameters to service', async () => {
// Arrange - Set up request with query params
req.query = {
categories: 'technology',
limit: '10'
};
mockGetTopNews.mockResolvedValue({
data: [],
meta: { found: 0, returned: 0 }
});
// Act - Call the controller method
await controller.getTopNews(req as Request, res as unknown as Response);
// Assert - Verify params were passed
// Note: the controller converts string values to appropriate types
expect(mockGetTopNews).toHaveBeenCalledWith(
expect.objectContaining({
categories: 'technology',
limit: 10 // Number conversion
})
);
});
});
describe('getNewsByUuid', () => {
test('returns article with 200 status when found', async () => {
// Arrange - Set up mock with UUID and data
const uuid = 'test-uuid-123';
req.params = { uuid };
mockGetNewsByUuid.mockResolvedValue({
data: { uuid, title: 'Test Article' }
});
// Act - Call the controller method
await controller.getNewsByUuid(req as Request, res as unknown as Response);
// Assert - Verify correct response
expect(mockGetNewsByUuid).toHaveBeenCalledWith(uuid);
expect(res.status).toHaveBeenCalledWith(200);
expect(res.json).toHaveBeenCalledWith(expect.objectContaining({
success: true,
data: expect.objectContaining({ uuid })
}));
});
test('returns 404 status when article not found', async () => {
// Arrange - Set up mock to simulate not found
const uuid = 'not-found-uuid';
req.params = { uuid };
mockGetNewsByUuid.mockRejectedValue(new Error('Article not found'));
// Act - Call the controller method
await controller.getNewsByUuid(req as Request, res as unknown as Response);
// Assert - Verify 404 response
expect(mockGetNewsByUuid).toHaveBeenCalledWith(uuid);
expect(res.status).toHaveBeenCalledWith(404);
expect(res.json).toHaveBeenCalledWith(expect.objectContaining({
success: false,
error: expect.any(String)
}));
});
});
});