shopify-testing

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Shopify App Testing

Shopify 应用测试

Reliable testing is crucial for ensuring your app handles Shopify's authentication and API quirks correctly.
可靠的测试对于确保你的应用正确处理Shopify的身份验证和API特殊逻辑至关重要。

1. Unit & Integration Testing (Vitest + Remix)

1. 单元与集成测试(Vitest + Remix)

Use Vitest for running unit and integration tests in the Remix environment.
使用Vitest在Remix环境中运行单元和集成测试。

Setup

安装配置

Install dependencies:
bash
npm install -D vitest @testing-library/react @testing-library/jest-dom jsdom
安装依赖:
bash
npm install -D vitest @testing-library/react @testing-library/jest-dom jsdom

Mocking
shopify.server.ts

模拟
shopify.server.ts

Creating a mock for the
authenticate
object is critical for testing loaders and actions without hitting real Shopify APIs.
typescript
// test/mocks/shopify.ts
import { vi } from 'vitest';

export const mockShopify = {
  authenticate: {
    admin: vi.fn(),
    public: vi.fn(),
    webhook: vi.fn(),
  },
};

// Usage in test file:
vi.mock('../app/shopify.server', () => mockShopify);

test('loader returns data for authenticated shop', async () => {
    mockShopify.authenticate.admin.mockResolvedValue({
        currentShop: 'test-shop.myshopify.com',
        session: { shop: 'test-shop.myshopify.com', accessToken: 'fake_token' },
        admin: {
             graphql: vi.fn().mockResolvedValue({ /* mock response */ })
        }
    });

    const response = await loader({ request: new Request('http://localhost/') });
    // Assertions...
});
authenticate
对象创建模拟对测试加载器和操作至关重要,无需调用真实的Shopify API。
typescript
// test/mocks/shopify.ts
import { vi } from 'vitest';

export const mockShopify = {
  authenticate: {
    admin: vi.fn(),
    public: vi.fn(),
    webhook: vi.fn(),
  },
};

// Usage in test file:
vi.mock('../app/shopify.server', () => mockShopify);

test('loader returns data for authenticated shop', async () => {
    mockShopify.authenticate.admin.mockResolvedValue({
        currentShop: 'test-shop.myshopify.com',
        session: { shop: 'test-shop.myshopify.com', accessToken: 'fake_token' },
        admin: {
             graphql: vi.fn().mockResolvedValue({ /* mock response */ })
        }
    });

    const response = await loader({ request: new Request('http://localhost/') });
    // Assertions...
});

2. Testing Loaders & Actions

2. 测试加载器与操作

Using Remix's
createRemixStub
or calling loaders/actions directly is the best way to test backend logic.
使用Remix的
createRemixStub
或者直接调用加载器/操作是测试后端逻辑的最佳方式。

Direct Function Call (Preferred for logic)

直接函数调用(逻辑测试首选)

You can import the
loader
or
action
and call it directly with a mock Request.
typescript
import { loader } from '../app/routes/app.dashboard';

test('dashboard loader returns stats', async () => {
   // Setup mocks...
   const response = await loader({ 
       request: new Request('http://localhost/app/dashboard'), 
       params: {} 
   });
   const data = await response.json();
   expect(data.stats).toBeDefined();
});
你可以导入
loader
action
并使用模拟的Request直接调用它。
typescript
import { loader } from '../app/routes/app.dashboard';

test('dashboard loader returns stats', async () => {
   // Setup mocks...
   const response = await loader({ 
       request: new Request('http://localhost/app/dashboard'), 
       params: {} 
   });
   const data = await response.json();
   expect(data.stats).toBeDefined();
});

3. End-to-End (E2E) Testing (Playwright)

3. 端到端(E2E)测试(Playwright)

For E2E tests, you need to handle the OAuth flow or bypass it using session tokens.
对于端到端测试,你需要处理OAuth流程,或者使用会话令牌绕过它。

Bypassing Auth (Session Token)

绕过身份验证(会话令牌)

The most stable way to E2E test embedded apps is to generate a valid session token (or mock the validation) so you don't have to automate the Login screen interaction which often triggers captchas.
对嵌入式应用进行端到端测试最稳定的方式是生成有效的会话令牌(或模拟验证过程),这样你就不需要自动化登录页面交互,因为登录操作经常会触发验证码。

Basic Playwright Test

基础Playwright测试

typescript
import { test, expect } from '@playwright/test';

test('load dashboard', async ({ page }) => {
  await page.goto('http://localhost:3000/app');
  // Expect to see the Polaris Page title
  await expect(page.locator('.Polaris-Page-Header__Title')).toHaveText('Dashboard');
});
typescript
import { test, expect } from '@playwright/test';

test('load dashboard', async ({ page }) => {
  await page.goto('http://localhost:3000/app');
  // Expect to see the Polaris Page title
  await expect(page.locator('.Polaris-Page-Header__Title')).toHaveText('Dashboard');
});

4. Testing Webhooks

4. 测试Webhooks

To test webhooks locally:
  1. Trigger via Shopify CLI:
    shopify app webhook trigger --topic ORDERS_CREATE
  2. Unit Test: Import the webhook action and pass a mock Request with the correct HMAC header.
typescript
import { action } from '../app/routes/webhooks';
import crypto from 'crypto';

test('webhook verifies hmac', async () => {
    const payload = JSON.stringify({ id: 123 });
    const hmac = crypto.createHmac('sha256', 'FULL_SECRET').update(payload).digest('base64');
    
    const request = new Request('http://localhost/webhooks', {
        method: 'POST',
        body: payload,
        headers: { 'X-Shopify-Hmac-Sha256': hmac }
    });

    // Mock authenticate.webhook to succeed
    // ...
    
    const response = await action({ request });
    expect(response.status).toBe(200);
});
要在本地测试webhook:
  1. 通过Shopify CLI触发
    shopify app webhook trigger --topic ORDERS_CREATE
  2. 单元测试:导入webhook操作并传入带有正确HMAC头部的模拟Request。
typescript
import { action } from '../app/routes/webhooks';
import crypto from 'crypto';

test('webhook verifies hmac', async () => {
    const payload = JSON.stringify({ id: 123 });
    const hmac = crypto.createHmac('sha256', 'FULL_SECRET').update(payload).digest('base64');
    
    const request = new Request('http://localhost/webhooks', {
        method: 'POST',
        body: payload,
        headers: { 'X-Shopify-Hmac-Sha256': hmac }
    });

    // Mock authenticate.webhook to succeed
    // ...
    
    const response = await action({ request });
    expect(response.status).toBe(200);
});