# Dependencias de testingbun add-d jest jest-expo @testing-library/react-native @testing-library/jest-native
# Tipos de TypeScriptbun add-d @types/jest
// __tests__/hooks/useDebounce.test.tsimport{ renderHook, act }from'@testing-library/react-native';import{ useState }from'react';// Hook de ejemplofunctionuseDebounce<T>(value:T, delay:number):T{const[debouncedValue, setDebouncedValue]=useState<T>(value); React.useEffect(()=>{const handler =setTimeout(()=>{setDebouncedValue(value);}, delay);return()=>clearTimeout(handler);},[value, delay]);return debouncedValue;}describe('useDebounce',()=>{beforeEach(()=>{ jest.useFakeTimers();});afterEach(()=>{ jest.useRealTimers();});it('returns initial value immediately',()=>{const{ result }=renderHook(()=>useDebounce('initial',500));expect(result.current).toBe('initial');});it('updates value after delay',()=>{let value ='initial';const{ result, rerender }=renderHook(()=>useDebounce(value,500)); value ='updated';rerender(()=>useDebounce(value,500));// Antes del delayexpect(result.current).toBe('initial');// Después del delayact(()=>{ jest.advanceTimersByTime(500);});expect(result.current).toBe('updated');});});
Testing de Contextos
Test de AppContext
// __tests__/contexts/AppContext.test.tsximport React from'react';import{ renderHook, act }from'@testing-library/react-native';import{ QueryClient, QueryClientProvider }from'@tanstack/react-query';import{ AppProvider, useApp }from'@/contexts/AppContext';// Wrapper con providers necesariosconstcreateWrapper=()=>{const queryClient =newQueryClient({ defaultOptions:{ queries:{ retry:false},},});return({ children }:{ children: React.ReactNode })=>(<QueryClientProvider client={queryClient}><AppProvider>{children}</AppProvider></QueryClientProvider>);};describe('AppContext',()=>{it('provides initial state',()=>{const{ result }=renderHook(()=>useApp(),{ wrapper:createWrapper(),});expect(result.current.favorites).toEqual([]);expect(result.current.user).toBeDefined();});it('toggleFavorite adds and removes favorites',()=>{const{ result }=renderHook(()=>useApp(),{ wrapper:createWrapper(),});// Agregar favoritoact(()=>{ result.current.toggleFavorite('item-1');});expect(result.current.isFavorite('item-1')).toBe(true);// Remover favoritoact(()=>{ result.current.toggleFavorite('item-1');});expect(result.current.isFavorite('item-1')).toBe(false);});it('updateUser updates user data',()=>{const{ result }=renderHook(()=>useApp(),{ wrapper:createWrapper(),});act(()=>{ result.current.updateUser({ name:'New Name'});});expect(result.current.user?.name).toBe('New Name');});});
Test de ThemeContext
// __tests__/contexts/ThemeContext.test.tsximport React from'react';import{ renderHook, act }from'@testing-library/react-native';import{ QueryClient, QueryClientProvider }from'@tanstack/react-query';import{ ThemeProvider, useTheme }from'@/contexts/ThemeContext';constcreateWrapper=()=>{const queryClient =newQueryClient({ defaultOptions:{ queries:{ retry:false}},});return({ children }:{ children: React.ReactNode })=>(<QueryClientProvider client={queryClient}><ThemeProvider>{children}</ThemeProvider></QueryClientProvider>);};describe('ThemeContext',()=>{it('provides colors based on theme',()=>{const{ result }=renderHook(()=>useTheme(),{ wrapper:createWrapper(),});expect(result.current.colors).toBeDefined();expect(result.current.colors.primary).toBeDefined();expect(result.current.colors.background).toBeDefined();});it('setTheme changes theme mode',()=>{const{ result }=renderHook(()=>useTheme(),{ wrapper:createWrapper(),});act(()=>{ result.current.setTheme('dark');});expect(result.current.themeMode).toBe('dark');expect(result.current.isDark).toBe(true);});it('toggles between light and dark',()=>{const{ result }=renderHook(()=>useTheme(),{ wrapper:createWrapper(),});const initialDark = result.current.isDark;act(()=>{ result.current.setTheme(initialDark ?'light':'dark');});expect(result.current.isDark).toBe(!initialDark);});});
# e2e/flows/home/browse-items.yamlappId: com.yourapp.app
---- launchApp
# Navegar a Explore-tapOn:id:"tab-explore"-assertVisible:"Explore"# Buscar un item-tapOn:id:"search-bar"-inputText:"React"-assertVisible:"React Native"# Abrir detalle-tapOn:"React Native"-assertVisible:"Item Details"# Volver-tapOn:id:"back-button"-assertVisible:"Explore"
Test de favoritos
# e2e/flows/home/favorites.yamlappId: com.yourapp.app
---- launchApp
# Ir a un item-tapOn:"First Item"# Agregar a favoritos-tapOn:id:"favorite-button"# Verificar toast-assertVisible:"Added to favorites"# Ir a favoritos-tapOn:id:"tab-favorites"# Verificar que aparece-assertVisible:"First Item"
Ejecutar tests
# Test individualmaestro test e2e/flows/auth/login.yaml
# Todos los testsmaestro test e2e/flows/
# Con grabaciónmaestro record e2e/flows/auth/login.yaml
# En CImaestro test--format junit e2e/flows/
Best Practices
1. Usar testID consistentemente
// components/Button.tsx<TouchableOpacity testID={testID ||'button'}.../>// En testsconst{ getByTestId }=render(<Button testID="submit-btn".../>);fireEvent.press(getByTestId('submit-btn'));