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.
使用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 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. 测试Webhooks
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);
});