shopify-testing
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseShopify 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 jsdomMocking shopify.server.ts
shopify.server.ts模拟shopify.server.ts
shopify.server.tsCreating a mock for the object is critical for testing loaders and actions without hitting real Shopify APIs.
authenticatetypescript
// 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...
});为对象创建模拟对于在不调用真实Shopify API的情况下测试加载器和操作至关重要。
authenticatetypescript
// 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 or calling loaders/actions directly is the best way to test backend logic.
createRemixStub使用Remix的或直接调用加载器/操作是测试后端逻辑的最佳方式。
createRemixStubDirect Function Call (Preferred for logic)
直接调用函数(推荐用于逻辑测试)
You can import the or and call it directly with a mock Request.
loaderactiontypescript
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();
});你可以导入或,并传入模拟的Request直接调用它。
loaderactiontypescript
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:
- Trigger via Shopify CLI:
shopify app webhook trigger --topic ORDERS_CREATE - 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的方法:
- 通过Shopify CLI触发:
shopify app webhook trigger --topic ORDERS_CREATE - 单元测试:导入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);
});