Sunday, November 16, 2025
Testing QuantaJS Applications: A Comprehensive Guide
Posted by
Testing QuantaJS Applications: A Comprehensive Guide
Testing is crucial for building reliable, maintainable applications. QuantaJS's framework-agnostic core and clear separation of concerns make it particularly testable. In this guide, we'll explore comprehensive strategies for testing QuantaJS applications, from unit testing stores to integration testing React components.
Why Test QuantaJS Applications?
QuantaJS applications benefit from testing because:
- Framework-Agnostic Core: Test stores independently without React
- Clear Separation: State, getters, and actions are easily testable
- Predictable Behavior: Reactive system has deterministic outcomes
- Type Safety: TypeScript support enables better test coverage
Testing Setup
Basic Testing Environment
# Install testing dependencies
npm install --save-dev vitest @testing-library/react @testing-library/jest-dom
# or
npm install --save-dev jest @testing-library/react @testing-library/jest-dom
Vitest Configuration
import { defineConfig } from 'vitest/config';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
test: {
environment: 'jsdom',
globals: true,
setupFiles: ['./src/test/setup.ts'],
},
});
1. Testing Stores (Core Package)
Since QuantaJS core is framework-agnostic, you can test stores without any React dependencies.
Testing Store State
import { describe, it, expect, beforeEach } from 'vitest';
import { createStore } from '@quantajs/core';
describe('Counter Store', () => {
let counterStore: ReturnType<typeof createStore>;
beforeEach(() => {
counterStore = createStore('counter', {
state: () => ({ count: 0 }),
actions: {
increment() { this.count++; },
decrement() { this.count--; },
reset() { this.count = 0; },
},
});
});
it('should initialize with count 0', () => {
expect(counterStore.count).toBe(0);
});
it('should increment count', () => {
counterStore.increment();
expect(counterStore.count).toBe(1);
});
it('should decrement count', () => {
counterStore.count = 5;
counterStore.decrement();
expect(counterStore.count).toBe(4);
});
it('should reset count to 0', () => {
counterStore.count = 10;
counterStore.reset();
expect(counterStore.count).toBe(0);
});
});
Testing Getters (Computed Values)
import { describe, it, expect, beforeEach } from 'vitest';
import { createStore } from '@quantajs/core';
describe('Todo Store Getters', () => {
let todoStore: ReturnType<typeof createStore>;
beforeEach(() => {
todoStore = createStore('todos', {
state: () => ({
todos: [
{ id: 1, text: 'Learn QuantaJS', done: false },
{ id: 2, text: 'Write tests', done: true },
{ id: 3, text: 'Build app', done: false },
],
}),
getters: {
activeTodos: (state) => state.todos.filter(t => !t.done),
completedTodos: (state) => state.todos.filter(t => t.done),
todoCount: (state) => state.todos.length,
completedCount: (state) => state.todos.filter(t => t.done).length,
},
});
});
it('should calculate active todos', () => {
expect(todoStore.activeTodos).toHaveLength(2);
expect(todoStore.activeTodos[0].text).toBe('Learn QuantaJS');
});
it('should calculate completed todos', () => {
expect(todoStore.completedTodos).toHaveLength(1);
expect(todoStore.completedTodos[0].text).toBe('Write tests');
});
it('should calculate total count', () => {
expect(todoStore.todoCount).toBe(3);
});
it('should calculate completed count', () => {
expect(todoStore.completedCount).toBe(1);
});
it('should update getters when state changes', () => {
todoStore.todos.push({ id: 4, text: 'New todo', done: false });
expect(todoStore.todoCount).toBe(4);
expect(todoStore.activeTodos).toHaveLength(3);
});
});
Testing Actions
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { createStore } from '@quantajs/core';
describe('User Store Actions', () => {
let userStore: ReturnType<typeof createStore>;
beforeEach(() => {
userStore = createStore('user', {
state: () => ({
user: null,
isLoading: false,
error: null,
}),
actions: {
async login(credentials) {
this.isLoading = true;
this.error = null;
try {
// Simulate API call
const response = await fetch('/api/login', {
method: 'POST',
body: JSON.stringify(credentials),
});
if (!response.ok) throw new Error('Login failed');
this.user = await response.json();
} catch (error) {
this.error = error.message;
} finally {
this.isLoading = false;
}
},
logout() {
this.user = null;
this.error = null;
},
},
});
});
it('should set loading state during login', async () => {
global.fetch = vi.fn(() =>
Promise.resolve({
ok: true,
json: () => Promise.resolve({ id: 1, name: 'John' }),
})
) as any;
const loginPromise = userStore.login({ email: 'test@example.com', password: 'pass' });
expect(userStore.isLoading).toBe(true);
await loginPromise;
expect(userStore.isLoading).toBe(false);
});
it('should set user on successful login', async () => {
const mockUser = { id: 1, name: 'John', email: 'john@example.com' };
global.fetch = vi.fn(() =>
Promise.resolve({
ok: true,
json: () => Promise.resolve(mockUser),
})
) as any;
await userStore.login({ email: 'test@example.com', password: 'pass' });
expect(userStore.user).toEqual(mockUser);
expect(userStore.error).toBeNull();
});
it('should handle login errors', async () => {
global.fetch = vi.fn(() =>
Promise.reject(new Error('Network error'))
) as any;
await userStore.login({ email: 'test@example.com', password: 'pass' });
expect(userStore.user).toBeNull();
expect(userStore.error).toBe('Network error');
expect(userStore.isLoading).toBe(false);
});
it('should clear user on logout', () => {
userStore.user = { id: 1, name: 'John' };
userStore.logout();
expect(userStore.user).toBeNull();
expect(userStore.error).toBeNull();
});
});
Testing Store Subscriptions
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { createStore } from '@quantajs/core';
describe('Store Subscriptions', () => {
it('should notify subscribers on state changes', () => {
const store = createStore('test', {
state: () => ({ count: 0 }),
actions: {
increment() { this.count++; },
},
});
const subscriber = vi.fn();
const unsubscribe = store.subscribe(subscriber);
store.increment();
expect(subscriber).toHaveBeenCalledTimes(1);
store.increment();
expect(subscriber).toHaveBeenCalledTimes(2);
unsubscribe();
store.increment();
expect(subscriber).toHaveBeenCalledTimes(2); // No more calls
});
it('should pass state snapshot to subscribers', () => {
const store = createStore('test', {
state: () => ({ value: 'initial' }),
actions: {
update(value) { this.value = value; },
},
});
const receivedStates: string[] = [];
store.subscribe((snapshot) => {
receivedStates.push(snapshot.value);
});
store.update('updated');
store.update('final');
expect(receivedStates).toEqual(['updated', 'final']);
});
});
2. Testing React Components
Testing Components with useStore
import { describe, it, expect, beforeEach } from 'vitest';
import { render, screen, fireEvent } from '@testing-library/react';
import { QuantaProvider } from '@quantajs/react';
import { createStore } from '@quantajs/core';
import { Counter } from '../Counter';
describe('Counter Component', () => {
let counterStore: ReturnType<typeof createStore>;
beforeEach(() => {
counterStore = createStore('counter', {
state: () => ({ count: 0 }),
actions: {
increment() { this.count++; },
decrement() { this.count--; },
},
});
});
it('should display initial count', () => {
render(
<QuantaProvider stores={{ counter: counterStore }}>
<Counter />
</QuantaProvider>
);
expect(screen.getByText('Count: 0')).toBeInTheDocument();
});
it('should increment count on button click', () => {
render(
<QuantaProvider stores={{ counter: counterStore }}>
<Counter />
</QuantaProvider>
);
const incrementButton = screen.getByText('+');
fireEvent.click(incrementButton);
expect(screen.getByText('Count: 1')).toBeInTheDocument();
});
it('should decrement count on button click', () => {
counterStore.count = 5;
render(
<QuantaProvider stores={{ counter: counterStore }}>
<Counter />
</QuantaProvider>
);
const decrementButton = screen.getByText('-');
fireEvent.click(decrementButton);
expect(screen.getByText('Count: 4')).toBeInTheDocument();
});
});
Testing Components with Selectors
import { describe, it, expect, beforeEach } from 'vitest';
import { render, screen } from '@testing-library/react';
import { QuantaProvider } from '@quantajs/react';
import { createStore } from '@quantajs/core';
import { TodoList } from '../TodoList';
describe('TodoList Component', () => {
let todoStore: ReturnType<typeof createStore>;
beforeEach(() => {
todoStore = createStore('todos', {
state: () => ({
todos: [
{ id: 1, text: 'Todo 1', done: false },
{ id: 2, text: 'Todo 2', done: true },
],
}),
getters: {
activeTodos: (state) => state.todos.filter(t => !t.done),
},
});
});
it('should render active todos only', () => {
render(
<QuantaProvider stores={{ todos: todoStore }}>
<TodoList />
</QuantaProvider>
);
expect(screen.getByText('Todo 1')).toBeInTheDocument();
expect(screen.queryByText('Todo 2')).not.toBeInTheDocument();
});
});
Testing Custom Hooks
import { describe, it, expect, beforeEach } from 'vitest';
import { renderHook, act } from '@testing-library/react';
import { QuantaProvider } from '@quantajs/react';
import { createStore } from '@quantajs/core';
import { useTodos } from '../hooks/useTodos';
describe('useTodos Hook', () => {
let todoStore: ReturnType<typeof createStore>;
beforeEach(() => {
todoStore = createStore('todos', {
state: () => ({ todos: [] }),
actions: {
addTodo(text) {
this.todos.push({ id: Date.now(), text, done: false });
},
},
});
});
it('should return todos from store', () => {
todoStore.todos = [{ id: 1, text: 'Test', done: false }];
const { result } = renderHook(() => useTodos(), {
wrapper: ({ children }) => (
<QuantaProvider stores={{ todos: todoStore }}>
{children}
</QuantaProvider>
),
});
expect(result.current.todos).toHaveLength(1);
expect(result.current.todos[0].text).toBe('Test');
});
it('should add todo', () => {
const { result } = renderHook(() => useTodos(), {
wrapper: ({ children }) => (
<QuantaProvider stores={{ todos: todoStore }}>
{children}
</QuantaProvider>
),
});
act(() => {
result.current.addTodo('New Todo');
});
expect(result.current.todos).toHaveLength(1);
expect(result.current.todos[0].text).toBe('New Todo');
});
});
3. Testing Persistence
Testing Persistence Configuration
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import { createStore, LocalStorageAdapter } from '@quantajs/core';
describe('Store Persistence', () => {
beforeEach(() => {
// Clear localStorage before each test
localStorage.clear();
vi.clearAllMocks();
});
afterEach(() => {
localStorage.clear();
});
it('should persist state to localStorage', async () => {
const store = createStore('test', {
state: () => ({ count: 0 }),
persist: {
adapter: new LocalStorageAdapter('test-store'),
debounceMs: 0, // No debounce for testing
},
});
store.count = 5;
// Wait for persistence to complete
await new Promise(resolve => setTimeout(resolve, 100));
const persisted = JSON.parse(localStorage.getItem('test-store') || '{}');
expect(persisted.data.count).toBe(5);
});
it('should restore state from localStorage', async () => {
// Set up initial persisted state
localStorage.setItem('test-store', JSON.stringify({
data: { count: 10 },
version: 1,
timestamp: Date.now(),
}));
const store = createStore('test', {
state: () => ({ count: 0 }),
persist: {
adapter: new LocalStorageAdapter('test-store'),
},
});
// Wait for restoration
await new Promise(resolve => setTimeout(resolve, 100));
expect(store.count).toBe(10);
});
it('should use include/exclude filters', async () => {
const store = createStore('test', {
state: () => ({
persist: 'yes',
temporary: 'no',
cache: 'no',
}),
persist: {
adapter: new LocalStorageAdapter('test-store'),
include: ['persist'],
debounceMs: 0,
},
});
await new Promise(resolve => setTimeout(resolve, 100));
const persisted = JSON.parse(localStorage.getItem('test-store') || '{}');
expect(persisted.data.persist).toBe('yes');
expect(persisted.data.temporary).toBeUndefined();
expect(persisted.data.cache).toBeUndefined();
});
});
4. Testing with Mocks and Fixtures
Creating Test Utilities
import { createStore, StoreInstance } from '@quantajs/core';
export function createTestStore<T extends object>(
name: string,
initialState: T
) {
return createStore(name, {
state: () => initialState,
});
}
export function createMockStore<T extends object>(
initialState: T
): StoreInstance<T, {}, {}> {
return {
...initialState,
state: initialState,
getters: {},
actions: {},
subscribe: vi.fn(() => vi.fn()),
$reset: vi.fn(),
} as any;
}
Using Test Fixtures
export const mockTodos = [
{ id: 1, text: 'Learn QuantaJS', done: false },
{ id: 2, text: 'Write tests', done: true },
{ id: 3, text: 'Build app', done: false },
];
export const createTodoStore = (initialTodos = mockTodos) => {
return createStore('todos', {
state: () => ({ todos: initialTodos }),
getters: {
activeTodos: (state) => state.todos.filter(t => !t.done),
},
actions: {
addTodo(text) {
this.todos.push({ id: Date.now(), text, done: false });
},
},
});
};
5. Integration Testing
Testing Store Interactions
import { describe, it, expect, beforeEach } from 'vitest';
import { createStore } from '@quantajs/core';
describe('Store Interactions', () => {
let userStore: ReturnType<typeof createStore>;
let cartStore: ReturnType<typeof createStore>;
beforeEach(() => {
userStore = createStore('user', {
state: () => ({ user: null }),
actions: {
login(user) { this.user = user; },
},
});
cartStore = createStore('cart', {
state: () => ({ items: [] }),
actions: {
addItem(item) { this.items.push(item); },
clearCart() { this.items = []; },
},
});
});
it('should clear cart when user logs out', () => {
cartStore.items = [{ id: 1, name: 'Product' }];
userStore.user = { id: 1, name: 'John' };
// Simulate logout
userStore.user = null;
cartStore.clearCart();
expect(cartStore.items).toHaveLength(0);
});
});
6. Testing Best Practices
✅ Do's
- Test Stores Independently: Test core logic without React
- Use Descriptive Test Names: Clear test descriptions help debugging
- Isolate Tests: Each test should be independent
- Test Edge Cases: Empty states, errors, boundary conditions
- Mock External Dependencies: API calls, localStorage, etc.
- Test Getters Separately: Computed values should have dedicated tests
- Test Actions in Isolation: Verify state changes and side effects
❌ Don'ts
- Don't Test Implementation Details: Test behavior, not internals
- Don't Over-Mock: Only mock what's necessary
- Don't Test Framework Code: Trust QuantaJS, test your code
- Don't Skip Error Cases: Test error handling thoroughly
- Don't Create Complex Test Setup: Keep tests simple and readable
7. Testing Patterns
Pattern: Testing Async Actions
it('should handle async actions correctly', async () => {
const store = createStore('test', {
state: () => ({ data: null, loading: false }),
actions: {
async fetchData() {
this.loading = true;
try {
this.data = await api.fetch();
} finally {
this.loading = false;
}
},
},
});
const promise = store.fetchData();
expect(store.loading).toBe(true);
await promise;
expect(store.loading).toBe(false);
expect(store.data).toBeDefined();
});
Pattern: Testing Computed Dependencies
it('should update computed values when dependencies change', () => {
const store = createStore('test', {
state: () => ({ a: 1, b: 2 }),
getters: {
sum: (state) => state.a + state.b,
},
});
expect(store.sum).toBe(3);
store.a = 5;
expect(store.sum).toBe(7);
});
Pattern: Testing Store Reset
it('should reset store to initial state', () => {
const store = createStore('test', {
state: () => ({ count: 0, name: 'Initial' }),
});
store.count = 10;
store.name = 'Changed';
store.$reset();
expect(store.count).toBe(0);
expect(store.name).toBe('Initial');
});
8. Coverage and CI/CD
Coverage Configuration
export default defineConfig({
test: {
coverage: {
provider: 'v8',
reporter: ['text', 'json', 'html'],
include: ['src/stores/**', 'src/components/**'],
exclude: ['**/*.test.ts', '**/*.test.tsx'],
},
},
});
Example Test Scripts
{
"scripts": {
"test": "vitest",
"test:ui": "vitest --ui",
"test:coverage": "vitest --coverage",
"test:watch": "vitest --watch"
}
}
Real-World Testing Example
import { describe, it, expect, beforeEach } from 'vitest';
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import { QuantaProvider } from '@quantajs/react';
import { createStore, LocalStorageAdapter } from '@quantajs/core';
import { ShoppingCart } from '../ShoppingCart';
describe('Shopping Cart E2E', () => {
let cartStore: ReturnType<typeof createStore>;
beforeEach(() => {
localStorage.clear();
cartStore = createStore('cart', {
state: () => ({ items: [], total: 0 }),
getters: {
itemCount: (state) => state.items.length,
},
actions: {
addItem(product) {
const existing = this.items.find(i => i.id === product.id);
if (existing) {
existing.quantity++;
} else {
this.items.push({ ...product, quantity: 1 });
}
this.total = this.items.reduce((sum, item) =>
sum + (item.price * item.quantity), 0
);
},
},
persist: {
adapter: new LocalStorageAdapter('cart'),
debounceMs: 0,
},
});
});
it('should add items to cart and persist', async () => {
render(
<QuantaProvider stores={{ cart: cartStore }}>
<ShoppingCart />
</QuantaProvider>
);
const addButton = screen.getByText('Add to Cart');
fireEvent.click(addButton);
await waitFor(() => {
expect(screen.getByText('Items: 1')).toBeInTheDocument();
});
// Verify persistence
const persisted = JSON.parse(localStorage.getItem('cart') || '{}');
expect(persisted.data.items).toHaveLength(1);
});
});
Conclusion
Testing QuantaJS applications is straightforward thanks to its framework-agnostic core and clear architecture. By following these patterns and best practices, you can build comprehensive test suites that ensure your application's reliability and maintainability.
Key takeaways:
- Test stores independently from React components
- Use selectors to test specific state slices
- Mock external dependencies like APIs and storage
- Test both happy paths and error cases
- Keep tests simple and focused
Remember: Good tests are a safety net that allows you to refactor and improve your code with confidence. Invest time in writing quality tests, and your future self will thank you!
Happy testing! 🧪