AuthProvider.test.tsx 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
  2. import { screen, act, fireEvent } from '@testing-library/react';
  3. import { AuthProvider, useAuth } from '../../src/hooks/AuthProvider';
  4. import React from 'react';
  5. import { renderWithProviders } from '../test-utils';
  6. // Mock axios
  7. vi.mock('axios', () => ({
  8. default: {
  9. defaults: {
  10. headers: {
  11. common: {},
  12. },
  13. },
  14. },
  15. }));
  16. // Mock react-query
  17. vi.mock('@tanstack/react-query', () => ({
  18. useQuery: vi.fn().mockReturnValue({
  19. isLoading: false,
  20. data: null,
  21. error: null,
  22. }),
  23. useQueryClient: () => ({
  24. clear: vi.fn(),
  25. }),
  26. }));
  27. // Mock auth client
  28. vi.mock('../../src/api/authClient', () => ({
  29. authClient: {
  30. login: {
  31. $post: vi.fn(),
  32. },
  33. logout: {
  34. $post: vi.fn(),
  35. },
  36. me: {
  37. $get: vi.fn(),
  38. },
  39. },
  40. }));
  41. // Mock localStorage
  42. const localStorageMock = {
  43. getItem: vi.fn(),
  44. setItem: vi.fn(),
  45. removeItem: vi.fn(),
  46. clear: vi.fn(),
  47. };
  48. Object.defineProperty(window, 'localStorage', {
  49. value: localStorageMock,
  50. });
  51. // Test component that uses useAuth
  52. const TestComponent = () => {
  53. const auth = useAuth();
  54. return (
  55. <div>
  56. <div data-testid="user">{auth.user ? 'authenticated' : 'unauthenticated'}</div>
  57. <div data-testid="isAuthenticated">{auth.isAuthenticated ? 'true' : 'false'}</div>
  58. <div data-testid="isLoading">{auth.isLoading ? 'true' : 'false'}</div>
  59. <button onClick={() => auth.login('test', 'password')}>Login</button>
  60. <button onClick={() => auth.logout()}>Logout</button>
  61. </div>
  62. );
  63. };
  64. describe('AuthProvider', () => {
  65. beforeEach(() => {
  66. vi.clearAllMocks();
  67. localStorageMock.getItem.mockReturnValue(null);
  68. });
  69. afterEach(() => {
  70. vi.restoreAllMocks();
  71. });
  72. it('应该提供认证上下文', () => {
  73. renderWithProviders(
  74. <AuthProvider>
  75. <TestComponent />
  76. </AuthProvider>
  77. );
  78. expect(screen.getByTestId('user')).toHaveTextContent('unauthenticated');
  79. expect(screen.getByTestId('isAuthenticated')).toHaveTextContent('false');
  80. });
  81. it('应该在没有AuthProvider时抛出错误', () => {
  82. // 抑制控制台错误输出
  83. const consoleError = vi.spyOn(console, 'error').mockImplementation(() => {});
  84. expect(() => {
  85. renderWithProviders(<TestComponent />);
  86. }).toThrow('useAuth必须在AuthProvider内部使用');
  87. consoleError.mockRestore();
  88. });
  89. it('应该处理登录成功', async () => {
  90. const mockLoginResponse = {
  91. status: 200,
  92. json: vi.fn().mockResolvedValue({
  93. token: 'test-token',
  94. user: { id: 1, username: 'test', email: 'test@example.com' },
  95. }),
  96. };
  97. const { authClient } = await import('../../src/api/authClient');
  98. (authClient.login.$post as any).mockResolvedValue(mockLoginResponse);
  99. renderWithProviders(
  100. <AuthProvider>
  101. <TestComponent />
  102. </AuthProvider>
  103. );
  104. const loginButton = screen.getByText('Login');
  105. await act(async () => {
  106. fireEvent.click(loginButton);
  107. });
  108. expect(authClient.login.$post).toHaveBeenCalledWith({
  109. json: {
  110. username: 'test',
  111. password: 'password',
  112. },
  113. });
  114. });
  115. it('应该处理登录失败', async () => {
  116. const mockLoginResponse = {
  117. status: 401,
  118. json: vi.fn().mockResolvedValue({
  119. message: 'Invalid credentials',
  120. }),
  121. };
  122. const { authClient } = await import('../../src/api/authClient');
  123. (authClient.login.$post as any).mockResolvedValue(mockLoginResponse);
  124. renderWithProviders(
  125. <AuthProvider>
  126. <TestComponent />
  127. </AuthProvider>
  128. );
  129. const loginButton = screen.getByText('Login');
  130. await act(async () => {
  131. fireEvent.click(loginButton);
  132. });
  133. expect(authClient.login.$post).toHaveBeenCalledWith({
  134. json: {
  135. username: 'test',
  136. password: 'password',
  137. },
  138. });
  139. });
  140. it('应该处理登出', async () => {
  141. const { authClient } = await import('../../src/api/authClient');
  142. (authClient.logout.$post as any).mockResolvedValue({});
  143. localStorageMock.getItem.mockReturnValue('test-token');
  144. renderWithProviders(
  145. <AuthProvider>
  146. <TestComponent />
  147. </AuthProvider>
  148. );
  149. const logoutButton = screen.getByText('Logout');
  150. await act(async () => {
  151. fireEvent.click(logoutButton);
  152. });
  153. expect(authClient.logout.$post).toHaveBeenCalled();
  154. expect(localStorageMock.removeItem).toHaveBeenCalledWith('token');
  155. });
  156. });
  157. // Helper function to fire events
  158. function fireEvent(element: HTMLElement, event: string) {
  159. element.dispatchEvent(new Event(event, { bubbles: true }));
  160. }