Skip to main content

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 / PathDescription
Golden Path (Recommended)Pre-installed as a devDependency
Uplift Pathnpm 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 LocationPurpose & Guidelines
src/components/Button.test.tsxReact test file, next to Button.tsx
src/app/features/login.component.spec.tsAngular test file, next to login.component.ts
reports/coverage/Git-ignored directory for test coverage reports

4. Helper - Pillars Alignment

PillarHow This Helper Aligns
IntuitivePrimary Pillar: Tests are written from a user's perspective to ensure predictability
AdaptiveEnables safe iteration and refactoring by having a comprehensive test suite
InclusiveEnsures 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

  1. Arrange: Setup component, props, and dependencies
  2. Act: Simulate user interaction
  3. 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 VariableDefault ValueDescriptionRecommended Value
coverage.threshold80Min % coverage required for CI pass80
reporters['default']Output formats (can include html, json, etc.)['default', 'html']

10. Key Scripts & CLI Commands

CommandWhat It DoesExample
npm run playtestRuns full test suite oncenpm run playtest
npm run playtest:watchStarts watch mode for live test re-runningnpm run playtest:watch
npx playtest:genScaffolds a new test file for a given componentnpx playtest:gen src/components/UserProfile.tsx

What playtest:gen Generates

React

  • Auto-imports for render and screen
  • 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.