Mock services activate automatically when API keys are missing. Add credentials via yarn setup to use real services - no code changes needed.
How It Works
// Backend mock activates based on provider and credentials
export const USE_MOCK_SUPABASE = __DEV__ && isSupabase && !process.env.EXPO_PUBLIC_SUPABASE_URL
export const USE_MOCK_CONVEX = __DEV__ && isConvex && !process.env.EXPO_PUBLIC_CONVEX_URL
// RevenueCat also detects placeholder values like "your-ios-key"
// This prevents SDK credential errors when using .env.example values
const isPlaceholderKey = (key) => !key || key.startsWith("your-")
Placeholder values like your-ios-key from .env.example will also trigger mock mode. This prevents SDK credential errors - you can safely copy .env.example to .env and the app will work in mock mode.
Available Mocks
| Service | Mock Behavior |
|---|
| Supabase | Auth, database, storage, realtime - all in-memory |
| Convex | Auth, queries, mutations - all in-memory |
| PostHog | Events logged to console, feature flags return false |
| Sentry | Errors logged to console |
| RevenueCat | Purchases always succeed, dev menu toggle for pro status |
Backend Mocks
Full simulation of auth, database, storage, and realtime.import { supabase } from '@/services/supabase'
// Same code works with mock or real Supabase
await supabase.auth.signInWithPassword({
email: 'test@example.com',
password: 'password'
})
const { data } = await supabase
.from('posts')
.select('*')
.eq('author_id', 'user-123')
await supabase.storage
.from('avatars')
.upload('avatar.jpg', file)
Test Accounts
{ email: 'demo@shipnative.app', password: 'demo123' }
{ email: 'test@shipnative.app', password: 'test123' }
Testing Utilities
import { mockSupabaseHelpers } from '@/services/mocks/supabase'
// Seed test data
mockSupabaseHelpers.seedTable('posts', [
{ id: 1, title: 'Test Post', author_id: 'user-123' },
])
// Clear all mock data
mockSupabaseHelpers.clearAll()
Full simulation of auth, queries, and mutations.import { useConvexAuth } from '@/hooks/convex'
import { useQuery, useMutation } from '@/hooks/convex'
// Auth works the same in mock mode
const { signInWithPassword, isAuthenticated } = useConvexAuth()
await signInWithPassword('test@example.com', 'password')
// Queries and mutations work with mock data
const posts = useQuery(api.posts.list)
const createPost = useMutation(api.posts.create)
Test Accounts
{ email: 'demo@shipnative.app', password: 'demo123' }
{ email: 'test@shipnative.app', password: 'test123' }
Testing Utilities
import { mockConvexHelpers } from '@/services/mocks/convex'
// Seed test data
mockConvexHelpers.seedTable('posts', [
{ _id: 'post-1', title: 'Test Post', authorId: 'user-123' },
])
// Clear all mock data
mockConvexHelpers.clearAll()
Other Service Mocks
RevenueCat
import { mockRevenueCat } from '@/services/mocks/revenueCat'
// Toggle pro status for testing
mockRevenueCat.setProStatus(true)
// Or use the Dev Menu (Cmd+D / Cmd+M)
PostHog
Events logged to console with [MockPostHog] prefix. Feature flags return false by default.
Sentry
Errors logged to console with [MockSentry] prefix instead of being sent to Sentry.
Console Logging
All mock operations log to console:
[MockSupabase] Sign in: user@example.com
[MockConvex] Query: api.posts.list
[MockPostHog] Event: button_clicked
[MockRevenueCat] Purchase: pro_monthly
Limitations
- Data clears on app restart
- Security policies (RLS, function auth) not enforced
- Network performance not simulated
- Realtime subscriptions simulated locally
For production testing, connect real services via yarn setup.
Switching to Real Services
The wizard prompts for credentials. Once configured, the app automatically uses real services - no code changes needed.
You can also manually set environment variables in apps/app/.env:
EXPO_PUBLIC_BACKEND_PROVIDER=supabase
EXPO_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
EXPO_PUBLIC_SUPABASE_PUBLISHABLE_KEY=your-key
EXPO_PUBLIC_BACKEND_PROVIDER=convex
EXPO_PUBLIC_CONVEX_URL=https://your-project.convex.cloud
Restart Metro after changing environment variables: yarn start --clear