Skip to main content

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

EnvironmentPurposeBrand ID
DevelopmentLocal testingUse test Brand ID
StagingPre-production testingUse staging Brand ID
ProductionLive usersProduction 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
  • success is true
  • rewardAmount matches expected (100 for account_creation)
  • walletAddress is 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_new is true on 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 TypeRewardTest Scenario
account_creation100 LTZUser signup
complete_survey75 LTZSurvey completion
newsletter_subscribe25 LTZNewsletter form
rate_experience50 LTZRating widget
subscribe_renewal200 LTZSubscription webhook
form_submit10 LTZGeneric 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

  1. Check environment variables are set
  2. Verify Brand ID is valid
  3. Check automation is enabled in Partner Portal
  4. Use unique test emails

Tests Passing Locally But Failing in CI

  1. Verify secrets are configured in CI
  2. Check network access to Loyalteez APIs
  3. Increase timeouts for CI environment

Flaky Tests

  1. Add proper waitFor conditions
  2. Use unique test data per run
  3. Mock external services


Need Help? Join our Discord or email [email protected]