<!-- Source: https://developers.memberstack.com/dom-package/testing -->
<!-- Markdown version of a Memberstack developer documentation page. Canonical HTML: https://developers.memberstack.com/dom-package/testing -->

# Testing Utilities

The `@memberstack/dom/testing` subpath provides mock factories, a mock SDK instance, and custom test matchers for unit testing your Memberstack integration. No API calls needed.

Works with Jest & Vitest

All testing utilities work with both Jest and Vitest. The custom matchers extend the `expect` API in both frameworks.

## Import

Testing utilities are available from the `@memberstack/dom/testing` subpath. No additional packages to install.

```javascript
import {
  createMockMemberstack,
  mockMember,
  mockPlan,
  mockPlanConnection,
  mockCard,
  mockPrice,
  mockApp,
  mockContentGroup,
  memberstackMatchers
} from '@memberstack/dom/testing';
```

## Mock Factories

Mock factories create realistic test data with sensible defaults. Pass an overrides object to customize any fields.

### mockMember(overrides?)

Creates a mock Member object with all fields populated.

```javascript
import { mockMember } from '@memberstack/dom/testing';

// Default member
const member = mockMember();
// {
//   id: "mem_abc123xyz",
//   verified: true,
//   profileImage: "",
//   auth: { email: "test@example.com", hasPassword: true, providers: [] },
//   loginRedirect: "/",
//   stripeCustomerId: "",
//   createdAt: "2025-01-01T00:00:00.000Z",  // dynamic: new Date().toISOString()
//   metaData: {},
//   customFields: {},
//   permissions: [],
//   planConnections: []
// }

// With overrides
const verifiedMember = mockMember({
  verified: true,
  auth: { email: 'pro@example.com', hasPassword: true, providers: [] },
  customFields: { 'first-name': 'Jane' }
});
```

### mockPlan(overrides?)

Creates a mock Plan object.

```javascript
import { mockPlan } from '@memberstack/dom/testing';

const plan = mockPlan();
// { id: "pln_abc123", name: "Test Plan", description: "A test plan", status: "PUBLISHED",
//   redirects: { afterLogin: "/", afterLogout: "/", afterSignup: "/" }, prices: [] }

const proPlan = mockPlan({
  name: 'Pro',
  prices: [mockPrice({ amount: 29, name: 'Monthly' })]
});
```

### mockPlanConnection(overrides?)

Creates a mock PlanConnection representing a member's subscription to a plan.

```javascript
import { mockPlanConnection } from '@memberstack/dom/testing';

const connection = mockPlanConnection();
// {
//   id: "conn_abc123",
//   active: true,
//   status: "ACTIVE",
//   planId: "pln_abc123",
//   type: "FREE",
//   payment: null
// }

// Member with an active plan
const memberWithPlan = mockMember({
  planConnections: [
    mockPlanConnection({ planId: 'pln_pro-plan' })
  ]
});
```

### Other Factories

```javascript
import { mockPrice, mockCard, mockApp, mockContentGroup } from '@memberstack/dom/testing';

// Mock a price tier
const price = mockPrice({ amount: 49, name: 'Annual', interval: { type: 'YEARLY', count: 1 } });

// Mock a saved card
const card = mockCard({ brand: 'visa', last4: '4242' });

// Mock app configuration
const app = mockApp({ name: 'My App', mode: 'sandbox' });

// Mock a content group (gated content)
const group = mockContentGroup({ name: 'Premium Content', key: 'premium' });
```

## createMockMemberstack(options?)

Creates a complete mock Memberstack SDK instance that behaves like the real thing but runs entirely in memory. All methods return promises and update internal state correctly.

```javascript
import { createMockMemberstack, mockMember, mockPlan } from '@memberstack/dom/testing';

const mockMs = createMockMemberstack({
  // Pre-set a logged-in member
  member: mockMember({
    auth: { email: 'test@example.com', hasPassword: true, providers: [] }
  }),
  // Available plans
  plans: [
    mockPlan({ id: 'pln_free', name: 'Free' }),
    mockPlan({ id: 'pln_pro', name: 'Pro' })
  ]
});

// Use it like the real SDK
const { data } = await mockMs.getCurrentMember();
console.log(data.auth.email); // "test@example.com"

// Login/logout update internal state
await mockMs.logout();
const { data: afterLogout } = await mockMs.getCurrentMember();
console.log(afterLogout); // null

await mockMs.loginMemberEmailPassword({
  email: 'test@example.com',
  password: 'any-password'
});
const { data: afterLogin } = await mockMs.getCurrentMember();
console.log(afterLogin.auth.email); // "test@example.com"
```

### Available Mock Methods

The mock instance supports these methods:

| Method | Behavior |
| --- | --- |
| `getCurrentMember()` | Returns the current mock member or null |
| `loginMemberEmailPassword()` | Sets the mock member as logged in |
| `signupMemberEmailPassword()` | Creates and sets a mock member |
| `logout()` | Clears the current member |
| `updateMember()` | Updates custom fields on the mock member |
| `getPlans()` | Returns the configured mock plans |
| `getPlan()` | Returns a specific plan by ID |
| `getApp()` | Returns the mock app configuration |
| `getRestrictedUrlGroups()` | Returns mock content groups |
| `onAuthChange(callback)` | Subscribes to auth state changes |

### Test Helpers

The mock instance also includes test-only helpers for manipulating state directly:

```javascript
// Directly set the current member (without triggering login flow)
mockMs._setMember(mockMember({ verified: true }));

// Inspect the internal mock state
const state = mockMs._getMockState();
console.log(state.member);  // Current member
console.log(state.plans);   // Available plans
```

## Custom Test Matchers

Extend Jest or Vitest with Memberstack-specific assertions for cleaner, more readable tests.

```javascript
import { memberstackMatchers, mockMember, mockPlanConnection } from '@memberstack/dom/testing';

// Register matchers (do this once, e.g., in a setup file)
expect.extend(memberstackMatchers);

// toBeLoggedIn() - asserts member is not null
const member = mockMember();
expect(member).toBeLoggedIn();
expect(null).not.toBeLoggedIn();

// toHavePlan(planId) - asserts member has a specific plan
const memberWithPlan = mockMember({
  planConnections: [mockPlanConnection({ planId: 'pln_pro' })]
});
expect(memberWithPlan).toHavePlan('pln_pro');
expect(memberWithPlan).not.toHavePlan('pln_enterprise');

// toBeVerified() - asserts member's email is verified
const verifiedMember = mockMember({ verified: true });
expect(verifiedMember).toBeVerified();
```

> 💡 **Tip:**
>
> Register the matchers once in your test setup file (e.g., `vitest.setup.ts` or `jest.setup.js`) so they're available in all tests.

## Full Test Example

Here's a complete example testing a component that uses Memberstack for authentication and plan checking.

```typescript
// auth.test.ts
import { describe, it, expect, beforeEach } from 'vitest';
import {
  createMockMemberstack,
  mockMember,
  mockPlan,
  mockPlanConnection,
  memberstackMatchers
} from '@memberstack/dom/testing';

expect.extend(memberstackMatchers);

describe('Authentication Flow', () => {
  let mockMs;

  beforeEach(() => {
    mockMs = createMockMemberstack({
      plans: [
        mockPlan({ id: 'pln_free', name: 'Free' }),
        mockPlan({ id: 'pln_pro', name: 'Pro' })
      ]
    });
  });

  it('should start logged out', async () => {
    const { data } = await mockMs.getCurrentMember();
    expect(data).not.toBeLoggedIn();
  });

  it('should login and return member', async () => {
    mockMs._setMember(mockMember({
      auth: { email: 'user@test.com', hasPassword: true, providers: [] }
    }));

    const { data } = await mockMs.getCurrentMember();
    expect(data).toBeLoggedIn();
    expect(data.auth.email).toBe('user@test.com');
  });

  it('should check plan access', async () => {
    const member = mockMember({
      planConnections: [
        mockPlanConnection({ planId: 'pln_pro', status: 'ACTIVE' })
      ]
    });
    mockMs._setMember(member);

    const { data } = await mockMs.getCurrentMember();
    expect(data).toHavePlan('pln_pro');
    expect(data).not.toHavePlan('pln_enterprise');
  });

  it('should logout and clear member', async () => {
    mockMs._setMember(mockMember());

    await mockMs.logout();

    const { data } = await mockMs.getCurrentMember();
    expect(data).not.toBeLoggedIn();
  });

  it('should list available plans', async () => {
    const { data } = await mockMs.getPlans();
    expect(data).toHaveLength(2);
    expect(data[0].name).toBe('Free');
    expect(data[1].name).toBe('Pro');
  });
});
```

## Using with Dependency Injection

For the mock to work with your app code, pass the Memberstack instance as a dependency rather than importing it directly. Here's a pattern that works with most frameworks.

```typescript
// memberstack.ts - your app's Memberstack wrapper
let instance = null;

export function setMemberstack(ms) {
  instance = ms;
}

export function getMemberstack() {
  return instance;
}

// main.ts - production code
import memberstack from '@memberstack/dom';
import { setMemberstack } from './memberstack';

const ms = await memberstack.init({ publicKey: 'pk_...' });
setMemberstack(ms);

// test.ts - test code
import { createMockMemberstack } from '@memberstack/dom/testing';
import { setMemberstack } from './memberstack';

beforeEach(() => {
  setMemberstack(createMockMemberstack({ /* options */ }));
});
```
