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.
在Remix环境中使用Vitest运行单元和集成测试。

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. 测试Webhook

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);
});