Play+ Unit Testing Guide: playtest
1. Philosophy: Testing with Confidence & Clarity
In the Play+ ecosystem, testing is not an afterthought—it's a core part of how we build trust in our work. Just as we aim to design intuitive and adaptive experiences, we also strive to validate them with clarity and confidence. Testing allows us to explore new ideas fearlessly, knowing that well-crafted tests provide a safety net against regressions and subtle bugs.
Our @playplus/testing-config
package sets the foundation for this approach. It offers a zero-setup, opinionated environment that encourages:
- Frictionless Development: Get started quickly with tools that just work.
- User-Centric Thinking: Write tests that simulate real user behavior, not internals.
- Continuous Integration: Ensure quality is enforced through automated pipelines.
This guide outlines how to write expressive, resilient unit tests for your Play+ React or Angular applications.
2. Package Information
The Play+ testing toolchain is provided via the @playplus/testing-config
package and is a core component of the Golden Path starter kits.
Package / Path | Description |
---|---|
Golden Path (Recommended) | Pre-installed as a devDependency |
Uplift Path | npm install --save-dev @playplus/testing-config |
3. Folder Reference
Play+ follows a test co-location model, meaning test files live directly alongside the code they are testing. This improves discoverability and encourages test creation during development.
File Location | Purpose & Guidelines |
---|---|
src/components/Button.test.tsx | React test file, next to Button.tsx |
src/app/features/login.component.spec.ts | Angular test file, next to login.component.ts |
reports/coverage/ | Git-ignored directory for test coverage reports |
4. Helper - Pillars Alignment
Pillar | How This Helper Aligns |
---|---|
Intuitive | Primary Pillar: Tests are written from a user's perspective to ensure predictability |
Adaptive | Enables safe iteration and refactoring by having a comprehensive test suite |
Inclusive | Ensures reliability for all users by testing diverse states and edge cases |
5. Tooling Overview
The playtest
toolchain is a set of pre-configured tools, patterns, and CLI helpers designed to abstract the plumbing of modern testing environments.
What It Automates
- Zero-Setup Environment: Pre-configured Vitest/Jest for React and Jasmine/Karma for Angular.
- Automated Test Scaffolding: CLI helper
playtest:gen
analyzes components and generates boilerplate. - Automated Enforcement: Tests run on pre-commit and are required in CI/CD for Golden Path.
- Consistent Mocking: Provides standard mocking for dependencies like
apiService
.
The goal is to make testing a fast, frictionless, and integral part of development.
6. The Play+ Testing Stack
React
- Test Runner: Vitest (or Jest)
- Testing Library: React Testing Library
- Assertions:
expect
with matchers from@testing-library/jest-dom
Angular
- Test Runner: Jasmine and Karma
- Testing Library: Angular
TestBed
- Assertions: Jasmine’s built-in matchers
7. Core Testing Principles
Follow the AAA Pattern
- Arrange: Setup component, props, and dependencies
- Act: Simulate user interaction
- Assert: Validate output matches expectations
Test Behavior, Not Implementation
Avoid relying on internal variables:
❌ Don’t
expect(component.state.value).toBe('hello');
✅ Do
expect(screen.getByRole('textbox')).toHaveValue('hello');
8. Mocking Dependencies: Isolating the Unit
Unit tests should be isolated from external dependencies like APIs.
If apiProxy
is detected, playtest:gen
will auto-generate mocks.
Example (React + Vitest)
import { vi } from 'vitest';
import { apiProxy } from '../lib/apiProxy';
vi.mock('../lib/apiProxy');
it('displays user name after fetch', async () => {
vi.mocked(apiProxy).mockResolvedValue({
json: () => Promise.resolve({ name: 'John Doe' }),
});
render(<UserProfile />);
expect(await screen.findByText('John Doe')).toBeInTheDocument();
});
9. Key Config Options
Config Variable | Default Value | Description | Recommended Value |
---|---|---|---|
coverage.threshold | 80 | Min % coverage required for CI pass | 80 |
reporters | ['default'] | Output formats (can include html , json , etc.) | ['default', 'html'] |
10. Key Scripts & CLI Commands
Command | What It Does | Example |
---|---|---|
npm run playtest | Runs full test suite once | npm run playtest |
npm run playtest:watch | Starts watch mode for live test re-running | npm run playtest:watch |
npx playtest:gen | Scaffolds a new test file for a given component | npx playtest:gen src/components/UserProfile.tsx |
What playtest:gen
Generates
React
- Auto-imports for
render
andscreen
- A
describe
block and default smoke test - Placeholders for props and mocks
Angular
- Full
TestBed
setup - Lifecycle boilerplate
- Mock hooks for services
11. Usage Examples
React
// src/components/Counter.test.tsx
import { render, screen, fireEvent } from '@testing-library/react';
import Counter from './Counter';
describe('Counter', () => {
beforeEach(() => {
render(<Counter />);
});
it('should render with an initial count of 0', () => {
expect(screen.getByText('Count: 0')).toBeInTheDocument();
});
it('should increment the count when the button is clicked', () => {
fireEvent.click(screen.getByRole('button', { name: /increment/i }));
expect(screen.getByText('Count: 1')).toBeInTheDocument();
});
});
Angular
// src/app/counter/counter.component.spec.ts
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { CounterComponent } from './counter.component';
describe('CounterComponent', () => {
let component: CounterComponent;
let fixture: ComponentFixture<CounterComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({ declarations: [CounterComponent] }).compileComponents();
fixture = TestBed.createComponent(CounterComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should render with a count of 0', () => {
const p = fixture.debugElement.query(By.css('p')).nativeElement;
expect(p.textContent).toContain('Count: 0');
});
it('should increment the count on click', () => {
const button = fixture.debugElement.query(By.css('button'));
button.triggerEventHandler('click', null);
fixture.detectChanges();
expect(component.count).toBe(1);
const p = fixture.debugElement.query(By.css('p')).nativeElement;
expect(p.textContent).toContain('Count: 1');
});
});
12. Developer Checklist
- Are you testing from the user’s perspective (DOM and behavior, not internals)?
- Are you following the AAA (Arrange, Act, Assert) pattern?
- Are dependencies like APIs properly mocked?
- Did you cover edge cases and negative flows?
- Are tests independent, fast, and refactor-proof?
- Did you run
npm run playtest
to validate before pushing?
13. Summary
Unit testing in Play+ isn’t just about preventing bugs—it's about unlocking creative confidence. With robust tooling, opinionated helpers, and automated pipelines, we make testing an asset—not a burden.
- 🧪 User-Centric: Focus on behaviors and outcomes
- 🔁 Fully Automated: Enforced via CI to protect quality
- ⚡ Zero-Setup: Start writing tests immediately
- 🔍 Isolated & Predictable: Mocks ensure accuracy
Write tests. Refactor freely. Ship with confidence.
Let your tests reflect the experience you're building—not just the code behind it.