or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

async-testing.mdconfiguration.mdhooks.mdindex.mdinteractions.mdmatchers.mdqueries.mdrendering.md

async-testing.mddocs/

0

# Async Testing Utilities

1

2

Built-in utilities for handling asynchronous behavior, waiting for conditions, and testing time-dependent components in React Native applications.

3

4

## Capabilities

5

6

### WaitFor Utility

7

8

Wait for expectations to pass with configurable polling and timeout options.

9

10

```typescript { .api }

11

/**

12

* Wait for expectation to pass with polling

13

* @param expectation - Function that should eventually not throw

14

* @param options - Waiting configuration options

15

* @returns Promise that resolves with expectation result

16

*/

17

function waitFor<T>(

18

expectation: () => T,

19

options?: WaitForOptions

20

): Promise<T>;

21

22

interface WaitForOptions {

23

/** Timeout in milliseconds (default: from config.asyncUtilTimeout) */

24

timeout?: number;

25

26

/** Polling interval in milliseconds (default: 50) */

27

interval?: number;

28

29

/** Error object for stack traces */

30

stackTraceError?: Error;

31

32

/** Custom timeout error handler */

33

onTimeout?: (error: Error) => Error;

34

}

35

```

36

37

**Usage Examples:**

38

39

```typescript

40

import { render, screen, waitFor, fireEvent } from "@testing-library/react-native";

41

42

test("waiting for async state changes", async () => {

43

const AsyncComponent = () => {

44

const [loading, setLoading] = useState(true);

45

const [data, setData] = useState(null);

46

47

useEffect(() => {

48

setTimeout(() => {

49

setData("Loaded data");

50

setLoading(false);

51

}, 1000);

52

}, []);

53

54

return loading ? <Text>Loading...</Text> : <Text>{data}</Text>;

55

};

56

57

render(<AsyncComponent />);

58

59

// Initially shows loading

60

expect(screen.getByText("Loading...")).toBeOnTheScreen();

61

62

// Wait for data to load

63

await waitFor(() => {

64

expect(screen.getByText("Loaded data")).toBeOnTheScreen();

65

});

66

67

// Loading should be gone

68

expect(screen.queryByText("Loading...")).not.toBeOnTheScreen();

69

});

70

71

test("waiting with custom timeout", async () => {

72

const SlowComponent = () => {

73

const [message, setMessage] = useState("Initial");

74

75

useEffect(() => {

76

setTimeout(() => setMessage("Updated"), 2000);

77

}, []);

78

79

return <Text>{message}</Text>;

80

};

81

82

render(<SlowComponent />);

83

84

// Wait with extended timeout

85

await waitFor(

86

() => {

87

expect(screen.getByText("Updated")).toBeOnTheScreen();

88

},

89

{ timeout: 3000, interval: 100 }

90

);

91

});

92

93

test("waiting for user interactions to complete", async () => {

94

const InteractiveComponent = () => {

95

const [count, setCount] = useState(0);

96

const [processing, setProcessing] = useState(false);

97

98

const handlePress = async () => {

99

setProcessing(true);

100

// Simulate async operation

101

await new Promise(resolve => setTimeout(resolve, 500));

102

setCount(prev => prev + 1);

103

setProcessing(false);

104

};

105

106

return (

107

<View>

108

<Text>Count: {count}</Text>

109

<Text>Status: {processing ? "Processing" : "Ready"}</Text>

110

<Pressable testID="increment" onPress={handlePress}>

111

<Text>Increment</Text>

112

</Pressable>

113

</View>

114

);

115

};

116

117

render(<InteractiveComponent />);

118

119

const button = screen.getByTestId("increment");

120

121

// Initial state

122

expect(screen.getByText("Count: 0")).toBeOnTheScreen();

123

expect(screen.getByText("Status: Ready")).toBeOnTheScreen();

124

125

// Press button

126

fireEvent.press(button);

127

128

// Wait for processing to start

129

await waitFor(() => {

130

expect(screen.getByText("Status: Processing")).toBeOnTheScreen();

131

});

132

133

// Wait for processing to complete and count to update

134

await waitFor(() => {

135

expect(screen.getByText("Count: 1")).toBeOnTheScreen();

136

expect(screen.getByText("Status: Ready")).toBeOnTheScreen();

137

});

138

});

139

```

140

141

### WaitFor with Custom Error Handling

142

143

Advanced waitFor usage with custom error handling and debugging.

144

145

```typescript { .api }

146

/**

147

* Custom timeout error handler

148

* @param error - Original timeout error

149

* @returns Modified error with additional context

150

*/

151

type TimeoutErrorHandler = (error: Error) => Error;

152

```

153

154

**Usage Examples:**

155

156

```typescript

157

test("custom error handling", async () => {

158

const FailingComponent = () => <Text>This will never change</Text>;

159

160

render(<FailingComponent />);

161

162

// Custom error with debugging info

163

await expect(

164

waitFor(

165

() => {

166

expect(screen.getByText("Non-existent")).toBeOnTheScreen();

167

},

168

{

169

timeout: 1000,

170

onTimeout: (error) => {

171

const elements = screen.getAllByText(/./);

172

const elementTexts = elements.map(el =>

173

el.children.join(" ")

174

);

175

176

return new Error(

177

`${error.message}\n\nAvailable elements:\n${elementTexts.join("\n")}`

178

);

179

}

180

}

181

)

182

).rejects.toThrow(/Available elements:/);

183

});

184

185

test("stack trace preservation", async () => {

186

const stackTraceError = new Error();

187

188

render(<Text>Static text</Text>);

189

190

await expect(

191

waitFor(

192

() => {

193

expect(screen.getByText("Dynamic text")).toBeOnTheScreen();

194

},

195

{

196

timeout: 100,

197

stackTraceError // Preserves original call site in stack trace

198

}

199

)

200

).rejects.toThrow();

201

});

202

```

203

204

### WaitForElementToBeRemoved

205

206

Wait for elements to be removed from the component tree.

207

208

```typescript { .api }

209

/**

210

* Wait for element(s) to be removed from the tree

211

* @param callback - Function returning element(s) or element(s) directly

212

* @param options - Waiting configuration options

213

* @returns Promise that resolves when element(s) are removed

214

*/

215

function waitForElementToBeRemoved<T>(

216

callback: (() => T) | T,

217

options?: WaitForOptions

218

): Promise<void>;

219

```

220

221

**Usage Examples:**

222

223

```typescript

224

test("waiting for element removal", async () => {

225

const DismissibleComponent = () => {

226

const [visible, setVisible] = useState(true);

227

228

useEffect(() => {

229

const timer = setTimeout(() => setVisible(false), 1000);

230

return () => clearTimeout(timer);

231

}, []);

232

233

return visible ? (

234

<View testID="dismissible">

235

<Text>This will disappear</Text>

236

</View>

237

) : null;

238

};

239

240

render(<DismissibleComponent />);

241

242

// Element is initially present

243

const element = screen.getByTestId("dismissible");

244

expect(element).toBeOnTheScreen();

245

246

// Wait for element to be removed

247

await waitForElementToBeRemoved(element);

248

249

// Element should no longer exist

250

expect(screen.queryByTestId("dismissible")).not.toBeOnTheScreen();

251

});

252

253

test("waiting for multiple elements removal", async () => {

254

const MultiDismissComponent = () => {

255

const [items, setItems] = useState([1, 2, 3]);

256

257

useEffect(() => {

258

const timer = setTimeout(() => {

259

setItems(prev => prev.filter(item => item !== 2));

260

}, 500);

261

return () => clearTimeout(timer);

262

}, []);

263

264

return (

265

<View>

266

{items.map(item => (

267

<Text key={item} testID={`item-${item}`}>

268

Item {item}

269

</Text>

270

))}

271

</View>

272

);

273

};

274

275

render(<MultiDismissComponent />);

276

277

// All items initially present

278

expect(screen.getAllByText(/Item/)).toHaveLength(3);

279

280

// Wait for specific item to be removed using callback

281

await waitForElementToBeRemoved(() =>

282

screen.getByTestId("item-2")

283

);

284

285

// Only items 1 and 3 should remain

286

expect(screen.getAllByText(/Item/)).toHaveLength(2);

287

expect(screen.queryByTestId("item-2")).not.toBeOnTheScreen();

288

});

289

290

test("removal with loading states", async () => {

291

const LoadingComponent = () => {

292

const [loading, setLoading] = useState(true);

293

const [data, setData] = useState(null);

294

295

const loadData = async () => {

296

await new Promise(resolve => setTimeout(resolve, 800));

297

setData("Loaded content");

298

setLoading(false);

299

};

300

301

useEffect(() => {

302

loadData();

303

}, []);

304

305

if (loading) {

306

return (

307

<View testID="loading-spinner">

308

<Text>Loading...</Text>

309

</View>

310

);

311

}

312

313

return (

314

<View testID="content">

315

<Text>{data}</Text>

316

</View>

317

);

318

};

319

320

render(<LoadingComponent />);

321

322

// Loading spinner is present

323

const spinner = screen.getByTestId("loading-spinner");

324

expect(spinner).toBeOnTheScreen();

325

326

// Wait for loading spinner to be removed

327

await waitForElementToBeRemoved(spinner);

328

329

// Content should now be visible

330

expect(screen.getByTestId("content")).toBeOnTheScreen();

331

expect(screen.getByText("Loaded content")).toBeOnTheScreen();

332

});

333

```

334

335

### FindBy Queries (Async Queries)

336

337

All query methods have findBy variants that automatically wait for elements to appear.

338

339

```typescript { .api }

340

/**

341

* Async versions of getBy queries that wait for elements to appear

342

* These combine getBy queries with waitFor functionality

343

*/

344

345

// Text queries

346

function findByText(text: string | RegExp, options?: TextMatchOptions & WaitForOptions): Promise<ReactTestInstance>;

347

function findAllByText(text: string | RegExp, options?: TextMatchOptions & WaitForOptions): Promise<ReactTestInstance[]>;

348

349

// TestID queries

350

function findByTestId(testId: string | RegExp, options?: TestIdOptions & WaitForOptions): Promise<ReactTestInstance>;

351

function findAllByTestId(testId: string | RegExp, options?: TestIdOptions & WaitForOptions): Promise<ReactTestInstance[]>;

352

353

// Role queries

354

function findByRole(role: string, options?: RoleOptions & WaitForOptions): Promise<ReactTestInstance>;

355

function findAllByRole(role: string, options?: RoleOptions & WaitForOptions): Promise<ReactTestInstance[]>;

356

357

// Label text queries

358

function findByLabelText(text: string | RegExp, options?: LabelTextOptions & WaitForOptions): Promise<ReactTestInstance>;

359

function findAllByLabelText(text: string | RegExp, options?: LabelTextOptions & WaitForOptions): Promise<ReactTestInstance[]>;

360

361

// Hint text queries

362

function findByHintText(text: string | RegExp, options?: HintTextOptions & WaitForOptions): Promise<ReactTestInstance>;

363

function findAllByHintText(text: string | RegExp, options?: HintTextOptions & WaitForOptions): Promise<ReactTestInstance[]>;

364

365

// Placeholder text queries

366

function findByPlaceholderText(text: string | RegExp, options?: PlaceholderTextOptions & WaitForOptions): Promise<ReactTestInstance>;

367

function findAllByPlaceholderText(text: string | RegExp, options?: PlaceholderTextOptions & WaitForOptions): Promise<ReactTestInstance[]>;

368

369

// Display value queries

370

function findByDisplayValue(value: string | RegExp, options?: DisplayValueOptions & WaitForOptions): Promise<ReactTestInstance>;

371

function findAllByDisplayValue(value: string | RegExp, options?: DisplayValueOptions & WaitForOptions): Promise<ReactTestInstance[]>;

372

```

373

374

**Usage Examples:**

375

376

```typescript

377

test("findBy queries for async elements", async () => {

378

const AsyncContentComponent = () => {

379

const [content, setContent] = useState(null);

380

381

useEffect(() => {

382

setTimeout(() => {

383

setContent("Dynamic content loaded");

384

}, 600);

385

}, []);

386

387

return (

388

<View>

389

{content ? (

390

<Text testID="dynamic-content">{content}</Text>

391

) : (

392

<Text>Loading content...</Text>

393

)}

394

</View>

395

);

396

};

397

398

render(<AsyncContentComponent />);

399

400

// Initially only loading text

401

expect(screen.getByText("Loading content...")).toBeOnTheScreen();

402

403

// Wait for dynamic content to appear

404

const dynamicText = await screen.findByText("Dynamic content loaded");

405

expect(dynamicText).toBeOnTheScreen();

406

407

// Alternative using testID

408

const dynamicElement = await screen.findByTestId("dynamic-content");

409

expect(dynamicElement).toBeOnTheScreen();

410

});

411

412

test("findBy with custom wait options", async () => {

413

const VerySlowComponent = () => {

414

const [visible, setVisible] = useState(false);

415

416

useEffect(() => {

417

setTimeout(() => setVisible(true), 2500);

418

}, []);

419

420

return visible ? <Text>Finally loaded</Text> : <Text>Still loading</Text>;

421

};

422

423

render(<VerySlowComponent />);

424

425

// Wait with extended timeout

426

const slowText = await screen.findByText("Finally loaded", {

427

timeout: 3000,

428

interval: 100

429

});

430

431

expect(slowText).toBeOnTheScreen();

432

});

433

434

test("findAll for multiple async elements", async () => {

435

const AsyncListComponent = () => {

436

const [items, setItems] = useState([]);

437

438

useEffect(() => {

439

const timer = setTimeout(() => {

440

setItems(["Item 1", "Item 2", "Item 3"]);

441

}, 500);

442

return () => clearTimeout(timer);

443

}, []);

444

445

return (

446

<View>

447

{items.map((item, index) => (

448

<Text key={index} testID={`list-item-${index}`}>

449

{item}

450

</Text>

451

))}

452

</View>

453

);

454

};

455

456

render(<AsyncListComponent />);

457

458

// Wait for all items to appear

459

const items = await screen.findAllByText(/Item \d/);

460

expect(items).toHaveLength(3);

461

462

// Alternative using testID pattern

463

const itemElements = await screen.findAllByTestId(/list-item-\d/);

464

expect(itemElements).toHaveLength(3);

465

});

466

```

467

468

### Act Utility

469

470

React act utility for properly wrapping state updates and effects in tests.

471

472

```typescript { .api }

473

/**

474

* React act utility for wrapping state updates

475

* Ensures all updates are flushed before assertions

476

* @param callback - Function containing state updates

477

* @returns Promise that resolves when updates are complete

478

*/

479

function act<T>(callback: () => T): Promise<T>;

480

function act<T>(callback: () => Promise<T>): Promise<T>;

481

482

/**

483

* Get current React act environment setting

484

* @returns Current act environment state

485

*/

486

function getIsReactActEnvironment(): boolean;

487

488

/**

489

* Set React act environment setting

490

* @param isReactActEnvironment - Whether to use act environment

491

*/

492

function setReactActEnvironment(isReactActEnvironment: boolean): void;

493

```

494

495

**Usage Examples:**

496

497

```typescript

498

import { render, screen, act, fireEvent } from "@testing-library/react-native";

499

500

test("using act for state updates", async () => {

501

const StateComponent = () => {

502

const [count, setCount] = useState(0);

503

504

return (

505

<View>

506

<Text>Count: {count}</Text>

507

<Pressable testID="increment" onPress={() => setCount(c => c + 1)}>

508

<Text>+</Text>

509

</Pressable>

510

</View>

511

);

512

};

513

514

render(<StateComponent />);

515

516

const button = screen.getByTestId("increment");

517

518

// Act is usually not needed with fireEvent as it's wrapped automatically

519

fireEvent.press(button);

520

expect(screen.getByText("Count: 1")).toBeOnTheScreen();

521

522

// Manual act usage for direct state updates (rarely needed)

523

await act(async () => {

524

// Direct component state manipulation

525

fireEvent.press(button);

526

fireEvent.press(button);

527

});

528

529

expect(screen.getByText("Count: 3")).toBeOnTheScreen();

530

});

531

532

test("act environment configuration", () => {

533

// Check current act environment

534

const currentEnv = getIsReactActEnvironment();

535

expect(typeof currentEnv).toBe("boolean");

536

537

// Temporarily disable act warnings

538

setReactActEnvironment(false);

539

expect(getIsReactActEnvironment()).toBe(false);

540

541

// Restore previous setting

542

setReactActEnvironment(currentEnv);

543

expect(getIsReactActEnvironment()).toBe(currentEnv);

544

});

545

```

546

547

### FlushMicroTasks Utility

548

549

Utility for flushing pending microtasks in test environments.

550

551

```typescript { .api }

552

/**

553

* Flush all pending microtasks

554

* Useful for ensuring all Promise.resolve() calls have completed

555

* @returns Promise that resolves when all microtasks are flushed

556

*/

557

function flushMicroTasks(): Promise<void>;

558

```

559

560

**Usage Examples:**

561

562

```typescript

563

import { render, screen, flushMicroTasks } from "@testing-library/react-native";

564

565

test("flushing microtasks", async () => {

566

const MicroTaskComponent = () => {

567

const [message, setMessage] = useState("Initial");

568

569

useEffect(() => {

570

Promise.resolve().then(() => {

571

setMessage("Updated via microtask");

572

});

573

}, []);

574

575

return <Text>{message}</Text>;

576

};

577

578

render(<MicroTaskComponent />);

579

580

// Initially shows initial message

581

expect(screen.getByText("Initial")).toBeOnTheScreen();

582

583

// Flush microtasks to process Promise.resolve()

584

await flushMicroTasks();

585

586

// Message should now be updated

587

expect(screen.getByText("Updated via microtask")).toBeOnTheScreen();

588

});

589

590

test("combining with act for complete updates", async () => {

591

const ComplexAsyncComponent = () => {

592

const [state, setState] = useState("start");

593

594

useEffect(() => {

595

// Microtask

596

Promise.resolve().then(() => setState("microtask"));

597

598

// Macrotask

599

setTimeout(() => setState("macrotask"), 0);

600

}, []);

601

602

return <Text>{state}</Text>;

603

};

604

605

render(<ComplexAsyncComponent />);

606

607

// Initial state

608

expect(screen.getByText("start")).toBeOnTheScreen();

609

610

// Flush microtasks first

611

await flushMicroTasks();

612

expect(screen.getByText("microtask")).toBeOnTheScreen();

613

614

// Then wait for macrotask

615

await waitFor(() => {

616

expect(screen.getByText("macrotask")).toBeOnTheScreen();

617

});

618

});

619

```

620

621

## Configuration and Best Practices

622

623

Global configuration affecting async utilities behavior.

624

625

```typescript { .api }

626

/**

627

* Configuration affecting async utilities

628

*/

629

interface Config {

630

/** Default timeout for waitFor and findBy queries (ms) */

631

asyncUtilTimeout: number;

632

633

/** Other config options... */

634

defaultIncludeHiddenElements: boolean;

635

concurrentRoot: boolean;

636

defaultDebugOptions?: Partial<DebugOptions>;

637

}

638

```

639

640

**Usage Examples:**

641

642

```typescript

643

import { configure, resetToDefaults } from "@testing-library/react-native";

644

645

test("configuring async timeouts", async () => {

646

// Set longer timeout for slow tests

647

configure({ asyncUtilTimeout: 5000 });

648

649

const VerySlowComponent = () => {

650

const [loaded, setLoaded] = useState(false);

651

652

useEffect(() => {

653

setTimeout(() => setLoaded(true), 4000);

654

}, []);

655

656

return loaded ? <Text>Finally ready</Text> : <Text>Loading...</Text>;

657

};

658

659

render(<VerySlowComponent />);

660

661

// This will use the configured 5 second timeout

662

await screen.findByText("Finally ready");

663

664

// Reset to defaults

665

resetToDefaults();

666

});

667

668

test("best practices for async testing", async () => {

669

const BestPracticeComponent = () => {

670

const [data, setData] = useState(null);

671

const [loading, setLoading] = useState(true);

672

const [error, setError] = useState(null);

673

674

const fetchData = async () => {

675

try {

676

setLoading(true);

677

setError(null);

678

679

// Simulate API call

680

await new Promise(resolve => setTimeout(resolve, 500));

681

682

if (Math.random() > 0.8) {

683

throw new Error("Random API error");

684

}

685

686

setData("Success data");

687

} catch (err) {

688

setError(err.message);

689

} finally {

690

setLoading(false);

691

}

692

};

693

694

useEffect(() => {

695

fetchData();

696

}, []);

697

698

if (loading) return <Text>Loading...</Text>;

699

if (error) return <Text>Error: {error}</Text>;

700

return <Text>Data: {data}</Text>;

701

};

702

703

render(<BestPracticeComponent />);

704

705

// Wait for loading to complete

706

await waitForElementToBeRemoved(() => screen.getByText("Loading..."));

707

708

// Check for either success or error state

709

await waitFor(() => {

710

const successElement = screen.queryByText(/Data:/);

711

const errorElement = screen.queryByText(/Error:/);

712

713

expect(successElement || errorElement).toBeTruthy();

714

});

715

});

716

```