Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 75 additions & 0 deletions src/services/article.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import {describe, it, expect, vi, beforeEach} from 'vitest';
import * as article from '@/services/article';

vi.mock('@/util/http', () => ({
get: vi.fn()
}));

import * as http from '@/util/http';

describe('article service', () => {
beforeEach(() => {
vi.clearAllMocks();
});

describe('query', () => {
it('should call http.get with articles endpoint', async () => {
const mockData = {articles: [], articlesCount: 0};
vi.mocked(http.get).mockResolvedValue(mockData as any);

const result = await article.query();

expect(http.get).toHaveBeenCalledWith('articles', undefined);
expect(result).toEqual(mockData);
});

it('should pass query params to http.get', async () => {
const mockData = {articles: [], articlesCount: 0};
vi.mocked(http.get).mockResolvedValue(mockData as any);

const params = {limit: 10, offset: 0, tag: 'react'};
await article.query(params);

expect(http.get).toHaveBeenCalledWith('articles', params);
});
});

describe('findByTitle', () => {
it('should fetch article by title and return article property', async () => {
const mockArticle = {title: 'Test Article', slug: 'test-article'};
vi.mocked(http.get).mockResolvedValue({article: mockArticle} as any);

const result = await article.findByTitle('test-article');

expect(http.get).toHaveBeenCalledWith('articles/test-article');
expect(result).toEqual(mockArticle);
});
});

describe('fetchCommentsByTitle', () => {
it('should fetch comments for an article', async () => {
const mockComments = [
{id: '1', body: 'Comment 1'},
{id: '2', body: 'Comment 2'}
];
vi.mocked(http.get).mockResolvedValue({comments: mockComments} as any);

const result = await article.fetchCommentsByTitle('test-article');

expect(http.get).toHaveBeenCalledWith('articles/test-article/comments');
expect(result).toEqual(mockComments);
});
});

describe('fetchTags', () => {
it('should fetch tags and return tags array', async () => {
const mockTags = ['react', 'typescript', 'vitest'];
vi.mocked(http.get).mockResolvedValue({tags: mockTags} as any);

const result = await article.fetchTags();

expect(http.get).toHaveBeenCalledWith('tags');
expect(result).toEqual(mockTags);
});
});
});
162 changes: 162 additions & 0 deletions src/util/http.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
import {describe, it, expect, vi, beforeEach, afterEach} from 'vitest';
import {fetchJSON, get, del, post, put} from '@/util/http';

describe('http utilities', () => {
let fetchMock: ReturnType<typeof vi.fn>;

beforeEach(() => {
fetchMock = vi.fn();
vi.stubGlobal('fetch', fetchMock);
});

afterEach(() => {
vi.unstubAllGlobals();
});

describe('fetchJSON', () => {
it('should make a request with correct headers', async () => {
const mockResponse = {
ok: true,
json: vi.fn().mockResolvedValue({data: 'test'})
};
fetchMock.mockResolvedValue(mockResponse);

const result = await fetchJSON('test');

expect(fetchMock).toHaveBeenCalledWith(
'https://api.realworld.io/api/test',
expect.objectContaining({
headers: expect.objectContaining({
'content-type': 'application/json',
accept: 'application/json'
})
})
);
expect(result).toEqual({data: 'test'});
});

it('should merge custom headers', async () => {
const mockResponse = {
ok: true,
json: vi.fn().mockResolvedValue({data: 'test'})
};
fetchMock.mockResolvedValue(mockResponse);

await fetchJSON('test', {
headers: {Authorization: 'Bearer token'}
});

expect(fetchMock).toHaveBeenCalledWith(
'https://api.realworld.io/api/test',
expect.objectContaining({
headers: expect.objectContaining({
'content-type': 'application/json',
accept: 'application/json',
Authorization: 'Bearer token'
})
})
);
});

it('should throw error when response is not ok', async () => {
const mockResponse = {
ok: false,
json: vi.fn().mockResolvedValue({message: 'Error message'})
};
fetchMock.mockResolvedValue(mockResponse);

await expect(fetchJSON('test')).rejects.toThrow('Error message');
});
});

describe('get', () => {
it('should make GET request', async () => {
const mockResponse = {
ok: true,
json: vi.fn().mockResolvedValue({data: 'test'})
};
fetchMock.mockResolvedValue(mockResponse);

await get('articles');

expect(fetchMock).toHaveBeenCalledWith(
'https://api.realworld.io/api/articles',
expect.objectContaining({method: 'get'})
);
});

it('should append query string when params provided', async () => {
const mockResponse = {
ok: true,
json: vi.fn().mockResolvedValue({data: 'test'})
};
fetchMock.mockResolvedValue(mockResponse);

await get('articles', {limit: 10, offset: 0});

expect(fetchMock).toHaveBeenCalledWith(
expect.stringContaining('limit=10'),
expect.objectContaining({method: 'get'})
);
});
});

describe('del', () => {
it('should make DELETE request', async () => {
const mockResponse = {
ok: true,
json: vi.fn().mockResolvedValue({success: true})
};
fetchMock.mockResolvedValue(mockResponse);

await del('articles/123');

expect(fetchMock).toHaveBeenCalledWith(
'https://api.realworld.io/api/articles/123',
expect.objectContaining({method: 'delete'})
);
});
});

describe('post', () => {
it('should make POST request with JSON body', async () => {
const mockResponse = {
ok: true,
json: vi.fn().mockResolvedValue({article: {id: 1}})
};
fetchMock.mockResolvedValue(mockResponse);

const data = {title: 'Test', body: 'Content'};
await post('articles', data);

expect(fetchMock).toHaveBeenCalledWith(
'https://api.realworld.io/api/articles',
expect.objectContaining({
method: 'post',
body: JSON.stringify(data)
})
);
});
});

describe('put', () => {
it('should make PUT request with JSON body', async () => {
const mockResponse = {
ok: true,
json: vi.fn().mockResolvedValue({article: {id: 1}})
};
fetchMock.mockResolvedValue(mockResponse);

const data = {title: 'Updated'};
await put('articles/123', data);

expect(fetchMock).toHaveBeenCalledWith(
'https://api.realworld.io/api/articles/123',
expect.objectContaining({
method: 'put',
body: JSON.stringify(data)
})
);
});
});
});
Loading