Testing Guide
Complete guide for testing your Loyalteez integration.
🎯 Test Before You Ship: Ensure your integration works perfectly before going live.
Testing Strategy
1. Unit Tests
Test individual functions and API calls
2. Integration Tests
Test complete user flows end-to-end
3. Manual Testing
Test in development environment
4. Production Smoke Tests
Verify production deployment
Test Environments
| Environment | Purpose | Brand ID |
|---|---|---|
| Development | Local testing | Use test Brand ID |
| Staging | Pre-production testing | Use staging Brand ID |
| Production | Live users | Production Brand ID |
Testing Event Tracking
Unit Test Example (Jest)
const { trackEvent } = require('./loyalteez');
// Mock fetch
global.fetch = jest.fn();
describe('Event Tracking', () => {
beforeEach(() => {
fetch.mockClear();
});
test('should track account_creation event', async () => {
const mockResponse = {
success: true,
eventId: '123',
rewardAmount: 100,
};
fetch.mockResolvedValueOnce({
ok: true,
json: async () => mockResponse,
});
const result = await trackEvent(
'account_creation',
'[email protected]'
);
expect(fetch).toHaveBeenCalledWith(
'https://api.loyalteez.app/loyalteez-api/manual-event',
expect.objectContaining({
method: 'POST',
headers: { 'Content-Type': 'application/json' },
})
);
expect(result.success).toBe(true);
expect(result.rewardAmount).toBe(100);
});
test('should handle rate limit errors', async () => {
fetch.mockResolvedValueOnce({
ok: false,
status: 429,
json: async () => ({
error: 'Rate limit exceeded',
}),
});
await expect(
trackEvent('account_creation', '[email protected]')
).rejects.toThrow();
});
test('should handle automation disabled', async () => {
fetch.mockResolvedValueOnce({
ok: false,
status: 403,
json: async () => ({
error: 'Automation disabled',
}),
});
await expect(
trackEvent('account_creation', '[email protected]')
).rejects.toThrow();
});
});
Testing Gasless Transactions
Integration Test Example
import { renderHook, act, waitFor } from '@testing-library/react';
import { useLoyalteez } from './useLoyalteez';
// Mock Privy
jest.mock('@privy-io/react-auth', () => ({
usePrivy: () => ({
user: { wallet: { address: '0x123...' } },
getAccessToken: jest.fn().mockResolvedValue('mock_token'),
authenticated: true,
}),
}));
describe('Gasless Transactions', () => {
test('should execute gasless transaction', async () => {
const { result } = renderHook(() => useLoyalteez());
// Mock gas relayer response
global.fetch = jest.fn().mockResolvedValueOnce({
ok: true,
json: async () => ({
success: true,
transactionHash: '0xabc...',
}),
});
let txResult;
await act(async () => {
txResult = await result.current.executeTransaction({
to: '0xContract...',
data: '0x...',
gasLimit: 300000,
});
});
expect(txResult.success).toBe(true);
expect(txResult.transactionHash).toBeTruthy();
});
test('should handle invalid Privy token', async () => {
const { result } = renderHook(() => useLoyalteez());
global.fetch = jest.fn().mockResolvedValueOnce({
ok: false,
status: 401,
json: async () => ({
error: 'Invalid or expired Privy token',
}),
});
await expect(
result.current.executeTransaction({
to: '0xContract...',
data: '0x...',
})
).rejects.toThrow();
});
});
Manual Testing Checklist
Event Tracking
Test Account Creation:
# Test with cURL
curl -X POST https://api.loyalteez.app/loyalteez-api/manual-event \
-H "Content-Type: application/json" \
-d '{
"brandId": "YOUR_BRAND_ID",
"eventType": "account_creation",
"userEmail": "[email protected]"
}'
Expected Response:
{
"success": true,
"eventId": "...",
"rewardAmount": 100,
"walletCreated": true,
"walletAddress": "0x..."
}
Verify:
- Response status is 200
-
successistrue -
rewardAmountmatches expected (100 for account_creation) -
walletAddressis returned - User can see LTZ balance in wallet
Newsletter Subscribe
curl -X POST https://api.loyalteez.app/loyalteez-api/manual-event \
-H "Content-Type: application/json" \
-d '{
"brandId": "YOUR_BRAND_ID",
"eventType": "newsletter_subscribe",
"userEmail": "[email protected]"
}'
Expected Response:
{
"success": true,
"rewardAmount": 25,
...
}
Rate Limiting
Test with same email twice:
# First call - should succeed
curl -X POST https://api.loyalteez.app/loyalteez-api/manual-event \
-H "Content-Type: application/json" \
-d '{
"brandId": "YOUR_BRAND_ID",
"eventType": "newsletter_subscribe",
"userEmail": "[email protected]"
}'
# Second call immediately - should be rate limited
curl -X POST https://api.loyalteez.app/loyalteez-api/manual-event \
-H "Content-Type: application/json" \
-d '{
"brandId": "YOUR_BRAND_ID",
"eventType": "newsletter_subscribe",
"userEmail": "[email protected]"
}'
Expected Second Response:
{
"error": "Rate limit exceeded",
"message": "Too many events from this email..."
}
Status Code: 429
Verify:
- First call succeeds (200)
- Second call is rate limited (429)
- Error message is clear
OAuth Pregeneration
# Test Discord wallet pregeneration
curl -X POST https://register.loyalteez.app/loyalteez-api/pregenerate-user \
-H "Content-Type: application/json" \
-d '{
"brand_id": "YOUR_BRAND_ID",
"oauth_provider": "discord",
"oauth_user_id": "REAL_DISCORD_ID",
"oauth_username": "real_username"
}'
Expected Response:
{
"success": true,
"wallet_address": "0x...",
"privy_user_id": "...",
"created_new": true,
"message": "Wallet created for real_username"
}
Verify:
- Wallet address is returned
-
created_newistrueon first call - Same call returns
created_new: false(idempotent)
Frontend Testing
React Testing Library Example
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import EventTracker from './EventTracker';
// Mock Privy
jest.mock('@privy-io/react-auth', () => ({
usePrivy: () => ({
user: { email: { address: '[email protected]' } },
authenticated: true,
}),
PrivyProvider: ({ children }) => <div>{children}</div>,
}));
describe('EventTracker Component', () => {
test('should render event type dropdown', () => {
render(<EventTracker />);
const select = screen.getByRole('combobox');
expect(select).toBeInTheDocument();
});
test('should track event on button click', async () => {
// Mock fetch
global.fetch = jest.fn().mockResolvedValueOnce({
ok: true,
json: async () => ({
success: true,
rewardAmount: 100,
}),
});
render(<EventTracker />);
const button = screen.getByText(/Track Event/i);
fireEvent.click(button);
await waitFor(() => {
expect(fetch).toHaveBeenCalled();
});
// Should show success message
expect(screen.getByText(/100 LTZ/i)).toBeInTheDocument();
});
test('should show error on failure', async () => {
global.fetch = jest.fn().mockResolvedValueOnce({
ok: false,
status: 403,
json: async () => ({
error: 'Automation disabled',
}),
});
render(<EventTracker />);
const button = screen.getByText(/Track Event/i);
fireEvent.click(button);
await waitFor(() => {
expect(screen.getByText(/Automation disabled/i)).toBeInTheDocument();
});
});
});
End-to-End Testing
Playwright Example
import { test, expect } from '@playwright/test';
test.describe('Loyalteez Integration E2E', () => {
test('should complete full user flow', async ({ page }) => {
// 1. Navigate to app
await page.goto('http://localhost:3000');
// 2. Login with Privy
await page.click('button:has-text("Log In")');
await page.fill('input[type="email"]', '[email protected]');
await page.click('button:has-text("Continue")');
// Wait for authentication
await expect(page.locator('text=Welcome')).toBeVisible();
// 3. Track an event
await page.selectOption('select', 'account_creation');
await page.click('button:has-text("Track Event")');
// 4. Verify success
await expect(page.locator('text=100 LTZ')).toBeVisible({
timeout: 10000,
});
// 5. Check wallet balance
await page.click('button:has-text("Refresh")');
await expect(page.locator('text=/LTZ Balance: \\d+/i')).toBeVisible();
// 6. Claim a perk
await page.fill('input[type="number"]', '1');
await page.click('button:has-text("Claim Perk")');
// 7. Verify transaction
await expect(page.locator('text=Perk Claimed')).toBeVisible({
timeout: 30000, // Blockchain confirmation can take time
});
});
test('should handle errors gracefully', async ({ page }) => {
await page.goto('http://localhost:3000');
// Try to track event without login
await page.click('button:has-text("Track Event")');
await expect(page.locator('text=Please log in')).toBeVisible();
});
});
Load Testing
Test API Performance
Using Apache Bench:
# Test event tracking endpoint
ab -n 1000 -c 10 -p event.json -T application/json \
https://api.loyalteez.app/loyalteez-api/manual-event
event.json:
{
"brandId": "YOUR_BRAND_ID",
"eventType": "form_submit",
"userEmail": "[email protected]"
}
Using Artillery:
artillery-test.yml:
config:
target: "https://api.loyalteez.app"
phases:
- duration: 60
arrivalRate: 10
name: "Sustained load"
scenarios:
- name: "Track Events"
flow:
- post:
url: "/loyalteez-api/manual-event"
json:
brandId: "YOUR_BRAND_ID"
eventType: "form_submit"
userEmail: "loadtest{{ $randomNumber() }}@example.com"
Run:
artillery run artillery-test.yml
Test Data
Valid Test Emails
Use unique emails for each test to avoid rate limits:
function generateTestEmail(testName) {
return `test+${testName}+${Date.now()}@example.com`;
}
// Usage
const email = generateTestEmail('signup');
// [email protected]
Event Types & Rewards
| Event Type | Reward | Test Scenario |
|---|---|---|
account_creation | 100 LTZ | User signup |
complete_survey | 75 LTZ | Survey completion |
newsletter_subscribe | 25 LTZ | Newsletter form |
rate_experience | 50 LTZ | Rating widget |
subscribe_renewal | 200 LTZ | Subscription webhook |
form_submit | 10 LTZ | Generic forms |
Debugging Tests
Enable Debug Logging
// For fetch requests
const originalFetch = global.fetch;
global.fetch = async (...args) => {
console.log('📤 Fetch Request:', args[0], args[1]?.body);
const response = await originalFetch(...args);
console.log('📥 Fetch Response:', response.status);
return response;
};
Common Test Failures
"Cannot read properties of undefined"
Cause: Privy not properly mocked
Fix: Mock usePrivy hook completely
"Request timeout"
Cause: Network issue or API down
Fix: Increase timeout, check API status
"Rate limit exceeded"
Cause: Using same test email multiple times
Fix: Generate unique emails per test
"Automation disabled"
Cause: Testing with production Brand ID that has automation off
Fix: Use test Brand ID with automation enabled
CI/CD Integration
GitHub Actions Example
.github/workflows/test.yml:
name: Test
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
- name: Install dependencies
run: npm ci
- name: Run unit tests
run: npm test
env:
LOYALTEEZ_BRAND_ID: ${{ secrets.TEST_BRAND_ID }}
LOYALTEEZ_API_URL: https://api.loyalteez.app
- name: Run E2E tests
run: npm run test:e2e
env:
PRIVY_APP_ID: ${{ secrets.PRIVY_APP_ID }}
Production Testing
Smoke Tests After Deployment
Run these immediately after deploying:
# 1. Health check
curl https://yourapp.com/health
# 2. Test event tracking
curl -X POST https://api.loyalteez.app/loyalteez-api/manual-event \
-H "Content-Type: application/json" \
-d '{
"brandId": "PRODUCTION_BRAND_ID",
"eventType": "form_submit",
"userEmail": "[email protected]"
}'
# 3. Verify response
# Should return 200 with success: true
Monitoring
Set up alerts for:
- API error rate > 5%
- Event tracking failures
- Rate limit hits
- Authentication errors
Test Checklist
Before going live, verify:
- ✅ Unit tests pass
- ✅ Integration tests pass
- ✅ E2E tests pass
- ✅ Event tracking works for all event types
- ✅ Rate limiting works correctly
- ✅ Error handling works (automation disabled, rate limits)
- ✅ Gasless transactions work
- ✅ Wallet creation works
- ✅ LTZ rewards are distributed
- ✅ Production smoke tests pass
- ✅ Monitoring is set up
Troubleshooting
Tests Failing Locally
- Check environment variables are set
- Verify Brand ID is valid
- Check automation is enabled in Partner Portal
- Use unique test emails
Tests Passing Locally But Failing in CI
- Verify secrets are configured in CI
- Check network access to Loyalteez APIs
- Increase timeouts for CI environment
Flaky Tests
- Add proper
waitForconditions - Use unique test data per run
- Mock external services
Related Documentation
Need Help? Join our Discord or email [email protected]