Defines test file organization and principles for React Native applications.
Install
mkdir -p .claude/skills/react-testing-conventions && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/14646" && unzip -o skill.zip -d .claude/skills/react-testing-conventions && rm skill.zipInstalls to .claude/skills/react-testing-conventions
Activation
This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.
Testing conventions for React Native with TypeScript. Use this skill when writing tests, reviewing test code, or setting up test infrastructure. Covers unit tests, integration tests, component tests, test doubles (dummy, stub, spy, mock), builder pattern for fixtures, and the testing pyramid.Key capabilities
- →Organize test files by colocation
- →Name unit tests with `.test.ts` extension
- →Name E2E tests with `.yaml` extension
- →Follow the Arrange, Act, Assert (AAA) pattern
- →Name tests with 'should [expected behavior] when [condition]' pattern
- →Use test doubles (dummy, stub, spy, mock) appropriately
How it works
The skill establishes core principles like the testing pyramid and FIRST principles, defines file organization and naming conventions, and outlines the anatomy of a test including the AAA pattern and test naming. It also guides the use of test doubles.
Inputs & outputs
When to use react-testing-conventions
- →Writing unit tests
- →Organizing test files
- →Implementing test double strategies
About this skill
Testing Conventions
Testing conventions for React Native applications with TypeScript.
Stack: Jest • React Native Testing Library • TypeScript
Core Principles
Testing Pyramid
Respect the testing pyramid — more unit tests, fewer integration tests, even fewer E2E tests:
/\
/ \ E2E (Maestro)
/----\ Few, slow, expensive
/ \
/--------\ Integration
/ \ Some, medium speed
/------------\
/ \ Unit (*.test.ts)
/________________\ Many, fast, cheap
FIRST Principles
Tests must be:
| Principle | Description |
|---|---|
| Fast | Tests run quickly — milliseconds, not seconds |
| Independent | Tests don't depend on each other, can run in any order |
| Repeatable | Same result every time, no flakiness |
| Self-validating | Pass or fail, no manual inspection needed |
| Timely | Written at the same time as the code (or before with TDD) |
File Organization
Naming
| Type | Extension | Example |
|---|---|---|
| Unit test | .test.ts | Login.usecase.test.ts |
| E2E test (Maestro) | .yaml | login-flow.yaml |
Colocation (Unit & Integration)
Test files live next to the files they test:
modules/authentication/core/usecases/
├── Login.usecase.ts
└── Login.usecase.test.ts
modules/authentication/ui/viewModels/
├── useLogin.viewModel.tsx
└── useLogin.viewModel.test.ts
modules/authentication/infrastructure/adapters/
├── AuthApi.adapter.ts
└── AuthApi.adapter.test.ts
E2E Tests (Maestro)
E2E tests live in a dedicated folder at project root:
tests/
├── flows/
│ ├── authentication/
│ │ ├── login-flow.yaml
│ │ └── logout-flow.yaml
│ └── events/
│ ├── create-event-flow.yaml
│ └── delete-event-flow.yaml
└── utils/
└── common-steps.yaml
Test Anatomy
AAA Pattern
Every test follows Arrange, Act, Assert:
it("should return user when credentials are valid", async () => {
// Arrange
const authRepository = new AuthRepositoryStub();
const useCase = new LoginUseCase(authRepository);
const credentials = { email: "[email protected]", password: "password123" };
// Act
const result = await useCase.execute(credentials);
// Assert
expect(result.success).toBe(true);
expect(result.data.email).toBe("[email protected]");
});
Test Naming
Pattern: should [expected behavior] when [condition]
// ✅ Good
it("should return failure when email is invalid", ...)
it("should disable button when form is incomplete", ...)
it("should invalidate queries when mutation succeeds", ...)
// ❌ Bad
it("test login", ...)
it("works", ...)
it("handles error", ...)
One logical assertion per test
// ✅ Good — one behavior tested
it("should return failure when password is too short", async () => {
const result = await useCase.execute({ email: "[email protected]", password: "123" });
expect(result.success).toBe(false);
expect(result.error.type).toBe("VALIDATION_ERROR");
});
// ❌ Bad — testing multiple unrelated behaviors
it("should validate all fields", async () => {
// Testing email validation
const result1 = await useCase.execute({ email: "", password: "valid123" });
expect(result1.success).toBe(false);
// Testing password validation
const result2 = await useCase.execute({ email: "[email protected]", password: "" });
expect(result2.success).toBe(false);
// Testing success case
const result3 = await useCase.execute({
email: "[email protected]",
password: "valid123",
});
expect(result3.success).toBe(true);
});
Test Doubles
When to use what
| Test Double | Use Case | Layer |
|---|---|---|
| Dummy | Placeholder, never actually used | Core, UI |
| Stub | Returns predefined data | Core, UI |
| Spy | Records calls, verifies interactions | Infrastructure |
| Mock | Spy + predefined behavior | Infrastructure |
Rule: Stub the Core, Mock the Infrastructure
// ✅ Core/UI tests — use stubs
// We test behavior, not implementation
const authRepository = new AuthRepositoryStub();
const useCase = new LoginUseCase(authRepository);
// ✅ Infrastructure tests — use spies/mocks
// We test the implementation itself
jest.spyOn(global, "fetch").mockResolvedValue(mockResponse);
Dummy
Never actually used, just satisfies the type:
class LoggerDummy implements Logger {
log(_message: string): void {
// Do nothing
}
}
// Usage
const useCase = new SomeUseCase(realDependency, new LoggerDummy());
Stub
Returns predefined data:
class AuthRepositoryStub implements AuthRepository {
async login(_params: LoginParams): Promise<Result<User, AuthError>> {
return ok({
id: "user-1",
email: "[email protected]",
displayName: "Test User",
});
}
async logout(): Promise<Result<void, AuthError>> {
return ok(undefined);
}
}
// Configurable stub
class AuthRepositoryStub implements AuthRepository {
private loginResult: Result<User, AuthError> = ok(userBuilder().build());
withLoginSuccess(user: User): this {
this.loginResult = ok(user);
return this;
}
withLoginFailure(error: AuthError): this {
this.loginResult = fail(error);
return this;
}
async login(_params: LoginParams): Promise<Result<User, AuthError>> {
return this.loginResult;
}
}
// Usage
const stub = new AuthRepositoryStub().withLoginFailure({
type: "INVALID_CREDENTIALS",
});
Spy / Mock (Infrastructure only)
// Testing an adapter's implementation
describe("AuthApiAdapter", () => {
it("should call fetch with correct parameters", async () => {
const fetchSpy = jest.spyOn(global, "fetch").mockResolvedValue({
ok: true,
json: async () => ({ id: "1", email: "[email protected]" }),
} as Response);
const adapter = new AuthApiAdapter();
await adapter.login({ email: "[email protected]", password: "password" });
expect(fetchSpy).toHaveBeenCalledWith(
"https://api.example.com/auth/login",
expect.objectContaining({
method: "POST",
body: JSON.stringify({
email: "[email protected]",
password: "password",
}),
})
);
});
});
Builder Pattern
Use builders to create test fixtures (entities, props, etc.):
Entity Builder
// modules/authentication/core/entities/User.builder.ts
import { User } from "./User.entity";
export const userBuilder = () => {
const user: User = {
id: "user-123",
email: "[email protected]",
displayName: "Default User",
createdAt: "2024-01-01T00:00:00Z",
};
const builder = {
id: (id: string) => {
user.id = id;
return builder;
},
email: (email: string) => {
user.email = email;
return builder;
},
displayName: (displayName: string) => {
user.displayName = displayName;
return builder;
},
createdAt: (createdAt: string) => {
user.createdAt = createdAt;
return builder;
},
build: () => user,
};
return builder;
};
// Usage
const user = userBuilder()
.email("[email protected]")
.displayName("Custom")
.build();
Props Builder
// modules/events/ui/components/EventCard.props.builder.ts
import { EventCardProps } from "./EventCard";
export const eventCardPropsBuilder = () => {
const props: EventCardProps = {
title: "Default Event",
date: "2024-06-15T10:00:00Z",
attendees: 10,
onPress: jest.fn(),
};
const builder = {
title: (title: string) => {
props.title = title;
return builder;
},
date: (date: string) => {
props.date = date;
return builder;
},
attendees: (attendees: number) => {
props.attendees = attendees;
return builder;
},
onPress: (onPress: VoidFunction) => {
props.onPress = onPress;
return builder;
},
build: () => props,
};
return builder;
};
Builder naming
| Type | File | Function |
|---|---|---|
| Entity | User.builder.ts | userBuilder() |
| Props | EventCard.props.builder.ts | eventCardPropsBuilder() |
| Params | LoginParams.builder.ts | loginParamsBuilder() |
Testing Hooks
Custom renderHook
Use the custom renderHook that provides dependencies. See references/test-utils.md for the full implementation.
// modules/app/react/renderHook.tsx
export function renderHook<Result, Props>(
renderCallback: (props: Props) => Result,
options?: {
initialProps?: Props;
wrapper?: ComponentType<{ children: ReactNode }>;
dependencies?: Partial<Dependencies>;
}
): RenderHookResult<Result, Props>;
Usage
import { act } from "@testing-library/react-native";
import { renderHook } from "@app/react/renderHook";
import { useLoginViewModel } from "./useLogin.viewModel";
describe("useLoginViewModel", () => {
it("should update state to success when login succeeds", async () => {
const authRepositoryStub = new AuthRepositoryStub().withLoginSuccess(
userBuilder().build()
);
const { result } = renderHook(() => useLoginViewModel(), {
dependencies: { authRepository: authRepositoryStub },
});
await act(async () => {
await result.current.handlers.login("[email protected]", "password123");
});
expect(result.c
---
*Content truncated.*
When not to use it
- →When testing implementation details directly
- →When over-mocking dependencies
- →When writing tests without assertions
Limitations
- →The conventions are specific to React Native with TypeScript
- →The E2E tests are specific to Maestro
- →The stack is limited to Jest, React Native Testing Library, and TypeScript
How it compares
This skill provides a standardized and principled approach to testing React Native applications, ensuring consistency and maintainability, unlike ad-hoc testing practices.
Compared to similar skills
react-testing-conventions side by side with the closest alternatives in the catalog.
| Skill | Installs | Updated | Safety | Difficulty |
|---|---|---|---|---|
| react-testing-conventions (this skill) | 0 | 7mo | No flags | Intermediate |
| frontend-mobile-development-component-scaffold | 1 | 4mo | No flags | Intermediate |
| tdd | 0 | 4mo | No flags | Intermediate |
| dependency-upgrade | 26 | 5mo | Review | Intermediate |
Try saying
Example prompts that trigger this skill in your AI assistant.
You might also like
frontend-mobile-development-component-scaffold
sickn33
You are a React component architecture expert specializing in scaffolding production-ready, accessible, and performant components. Generate complete component implementations with TypeScript, tests, s
tdd
JubaKitiashvili
Test-driven development workflow with Jest and React Native Testing Library
dependency-upgrade
wshobson
Manage major dependency version upgrades with compatibility analysis, staged rollout, and comprehensive testing. Use when upgrading framework versions, updating major dependencies, or managing breaking changes in libraries.
angular-best-practices
sickn33
Angular performance optimization and best practices guide. Use when writing, reviewing, or refactoring Angular code for optimal performance, bundle size, and rendering efficiency.
validate-typescript
BerryKuipers
Run TypeScript compiler type-checking (tsc --noEmit) to validate type safety and catch type errors. Works with any TypeScript project. Returns structured output with error counts, categories (type/syntax/import errors), and affected files. Used for quality gates and pre-commit validation.
ts-testing
johnlindquist
Design, implement, and maintain high‑value TypeScript test suites using popular JS/TS testing libraries. Use this skill whenever the user is adding tests, debugging failing tests, or refactoring code that should be covered by tests.