Exploring Infinite Possibilities on FrontEnd 🚀
Burak Sağlık
Search...

Tue Aug 12 2025

Gerçek Cihazda Gerçek Test: Detox ile React Native’de E2E Otomasyonu

Merhaba tekrar! React Native geliştirirken çoğumuzun yolu jest ile unit test yazmaya çıkar. Jest süper bir araç, ama genelde sadece küçük parçaları test ediyoruz, tüm uygulamanın gerçek dünyada nasıl çalıştığını görmek zor oluyor. Peki ya kullanıcı deneyimini, ekranlar arası akışı, gerçek etkileşimleri test etmek?

İşte bu noktada Detox devreye giriyor. Detox ile sadece küçük birim değil, uygulamanın tamamını gerçek cihaz veya emülatörde otomatik testlerle sınayabilirsin. Bu sayede hataları erkenden yakalar, kullanıcıların yaşayacağı sürprizleri önlersin.

Bu yazıda, jest’ten bir adım öteye geçip Detox ile React Native uygulamana uçtan uca testler yazmanın tüm aşamalarını, kurulumdan test senaryolarına kadar pratik ve samimi şekilde anlatacağım. Hazırsan, testlerin gücünü keşfetmeye başlayalım!


Detox Nedir? Neden Detox Kullanmalıyız?

React Native ile uygulama geliştirirken, bir yandan kod yazarken diğer yandan yazdığımız kodun gerçekten düzgün çalıştığından emin olmak isteriz. İşte tam bu noktada testler devreye girer. Pek çoğumuz jest ile birim test (unit test) yaparız; küçük fonksiyonların, komponentlerin doğru çalışıp çalışmadığını kontrol ederiz. Ancak gerçek kullanıcı deneyimini test etmek jest ile mümkün değil.

Burada Detox devreye giriyor.

Detox, React Native uygulamalar için özel geliştirilmiş, gerçek cihazlar veya simülatörlerde çalışan uçtan uca (End-to-End, E2E) test otomasyon çerçevesidir. Yani, uygulamanın tamamını bir kullanıcının gözünden test etmeni sağlar.

Peki Detox’un farkı ne?

  • Detox’u gerçekten çok seviyorum çünkü uygulamanı gerçek cihazlarda ya da emülatörlerde açıyor ve tam bir kullanıcı gibi davranıyor: dokunuyor, kaydırıyor, yazıyor… Yani “Gerçek hayatta kullanıcı ne yapıyorsa, Detox da aynısını yapıyor” diyebilirim.

  • Bir başka güzel yanı da testlerin uygulama ile senkronize çalışması. Mesela “Butona bastım, ekran açıldı mı?” diye düşünmene gerek kalmıyor; Detox sana bu sorunun net cevabını veriyor. Böylece testlerin her zaman tutarlı ve güvenilir oluyor, sonuçlara güvenmek çok daha kolay.

  • Üstelik testleri komut satırından ya da otomatikleşmiş sistemlerden (CI/CD) kolayca çalıştırabiliyorum. Yani “Aman testleri tekrar tekrar manuel yapmak zorundayım” derdi tamamen ortadan kalkıyor. Jest ile birlikte kullandığında da geliştirici olarak işin gerçekten çok kolaylaşıyor.

Kısacası Detox, uygulamanın “kullanıcı gibi davranan robotu” gibi düşünebilirsin. Test senaryolarını yazıyorsun, Detox ise gerçek bir insanmış gibi adım adım uyguluyor ve sonunda “Her şey yolunda mı?” diye sana güvenilir bir rapor veriyor.

Hadi Birlikte Adım Adım Gerçek Bir Proje Üzerinde Öğrenelim!

Teorik bilgiler güzel ama en iyisi pratik yapmak, değil mi? Şimdi beraber basit ama gerçekçi bir React Native projesi üzerinden Detox ile uçtan uca test yazmayı öğreneceğiz.

Bu proje, içinde login olma, listeleme ve yeni kayıt ekleme gibi temel özellikler barındırıyor. Böylece, gerçek hayatta karşılaşacağın kullanıcı akışlarını test etmiş olacağız.

Adım adım ilerleyeceğiz; ne yapacağımızı, neden yaptığımızı anlayacaksın. Her satır kodun arkasındaki mantığı kavrayarak, kendi projende nasıl uygulayacağını göreceksin.

Hazırsan, başlayalım! 🚀

Proje Tanıtımı: Basit React Native Uygulaması

Bu rehberde, Detox ile uçtan uca testler yazarken kullanacağımız örnek projeyi de seninle paylaşıyorum. Projemiz oldukça sade ve temel işlevleri içeriyor; böylece testlere odaklanmak kolay oluyor.

Projede neler var?

  • Login ekranı: Kullanıcı adı ve şifre girip basit bir doğrulama yapıyoruz.
  • Listeleme ekranı: Giriş sonrası basit bir liste görüntülüyorsun.
  • Ekleme ekranı: Listeye yeni kayıt ekleyebildiğin basit bir form.

Bu temel ekranlar, gerçek bir uygulamanın kritik akışlarını simüle ediyor. Böylece Detox testlerini yazarken, gerçek kullanıcı etkileşimlerini taklit edebiliyoruz.

Projeyi Expo ile başlatıp, ardından Bare Workflow’a prebuild aşaması ile geçiş yapacağız. Böylece hem Expo’nun kolaylığından faydalanmış, hem de Detox’un gerektirdiği native yapı desteğini sağlamış olacağız.

1.Projeyi oluşturalım

  npx create-expo-app MyDetoxProject --template blank-typescript
  • npx create-expo-app: Expo projesi başlatır.
  • MyDetoxProject: Proje klasör adı.
  • --template blank-typescript: Boş ve TypeScript destekli template.

2.Proje klasörüne gir:

  cd MyDetoxProject

3. Projeyi başlat

🚨 Önemli: Detox ile çalışmadan önce Expo projesini prebuild yapmayı unutmayın.
Bu işlem, native iOS ve Android proje dosyalarını oluşturur.

Neden prebuild? Expo Managed projelerde native dosyalar yoktur, Detox ancak native projelerle çalışır. npx expo prebuild komutuyla native iOS ve Android dosyaları oluşturulur, böylece Detox testleri yapılabilir.

npx expo prebuild
//ios
  yarn ios
//android
  yarn android
  • Expo Metro Bundler açılır.
  • iOS/Android simülatör veya gerçek cihazda uygulamayı çalıştırabilirsin.

Projemiz dosya yapısı

  MyDetoxProject/
  ├── src/
  │ ├── screens/
  │ │ ├── LoginScreen.tsx
  │ │ ├── ListScreen.tsx
  │ │ └── AddItemScreen.tsx
  │ ├── components/
  │ │ └── ... (isteğe bağlı)
  │ └── navigation/
  │   └── AppNavigator.tsx
  ├── App.tsx
  ├── package.json
  ├── tsconfig.json
  └── ... diğer config dosyaları

Projede Detox ile test yazmak için temel ve kritik kod parçalarını aşağıda paylaşıyorum.
Detaylı tüm kodlar ve proje için GitHub reposunu inceleyebilirsiniz.

💡 Not: Detox’un elementleri bulabilmesi için bileşenlere testID eklemek gerekir.
Örneğin:

<TextInput testID="username-input" />

testID olmadan da bazı elementler bulunabilir, ancak bu hem kırılgan olur hem de test yazmayı zorlaştırır.
Kararlı ve okunabilir testler için testID kullanmak en güvenli yoldur.

//navigation/AppNavigator.tsx
//navigation/AppNavigator.tsx
<Stack.Navigator>
  <Stack.Screen name="Login" component={LoginScreen} />
  <Stack.Screen name="List">
    {(props) => <ListScreen {...props} items={items} />}
  </Stack.Screen>
  <Stack.Screen name="Add">
    {(props) => <AddScreen {...props} setItems={setItems} />}
  </Stack.Screen>
</Stack.Navigator>
//screens/AddItemScreen.tsx
//screens/AddItemScreen.tsx
<View style={styles.container}>
  <Text style={styles.title}>Yeni Kayıt Ekle</Text>

  <TextInput
    style={styles.input}
    placeholder="Başlık"
    value={title}
    onChangeText={setTitle}
    testID="title-input"
  />

  <TextInput
    style={[styles.input, { height: 80 }]}
    placeholder="Açıklama"
    value={desc}
    onChangeText={setDesc}
    multiline
    testID="description-input"
  />

  <Picker
    selectedValue={category}
    onValueChange={(itemValue) => setCategory(itemValue)}
    testID="category-picker"
  >
    <Picker.Item label="Genel" value="Genel" />
    <Picker.Item label="İş" value="İş" />
    <Picker.Item label="Kişisel" value="Kişisel" />
  </Picker>

  <Button title="Kaydet" onPress={handleAdd} testID="save-button" />
</View>
//screens/ListScreen.tsx
//screens/ListScreen.tsx
<View style={styles.container}>
  <Text style={styles.title}>Kayıtlar</Text>

  <TextInput
    style={styles.input}
    placeholder="Ara..."
    value={search}
    onChangeText={setSearch}
    testID="search-input"
  />

  <FlatList
    data={filteredItems}
    keyExtractor={(_, index) => index.toString()}
    renderItem={({ item }) => (
      <View style={styles.card}>
        <Text style={styles.cardTitle}>{item.title}</Text>
        <Text style={styles.cardDesc}>{item.description}</Text>
        <Text style={styles.cardCategory}>{item.category}</Text>
      </View>
    )}
    ListEmptyComponent={<Text>Kayıt yok</Text>}
  />

  <Button
    title="Yeni Ekle"
    onPress={() => navigation.navigate("Add")}
    testID="go-add-button"
  />
</View>
//screens/LoginScreen.tsx
//screens/LoginScreen.tsx
<View style={styles.container}>
  <TouchableWithoutFeedback onPress={Keyboard.dismiss}>
    <ScrollView
      testID="welcome"
      contentContainerStyle={{ flexGrow: 1, justifyContent: "center" }}
    >
      <Text testID="login-title" style={styles.title}>
        Giriş Yap
      </Text>
      <TextInput
        style={styles.input}
        placeholder="Kullanıcı Adı"
        value={username}
        onChangeText={setUsername}
        testID="username-input"
      />
      <TextInput
        style={styles.input}
        placeholder="Şifre"
        value={password}
        onChangeText={setPassword}
        secureTextEntry
        testID="password-input"
      />

      <View style={styles.switchContainer}>
        <Text>Beni hatırla</Text>
        <Switch
          value={remember}
          onValueChange={setRemember}
          testID="remember-switch"
        />
      </View>

      <Button title="Giriş" onPress={handleLogin} testID="login-button" />
    </ScrollView>
  </TouchableWithoutFeedback>
</View>

Project Screenshot

Artık Testlere Hazırız!

Kodlarımız hazır, proje ayağa kalktı. Şimdi sıra geldi Detox testlerini yazmaya! İlk olarak uygulamayı açacağız, ardından login olacağız. Sonrasında yeni bir kayıt ekleyeceğiz ve gerçekten eklenip eklenmediğini kontrol edeceğiz.

Yani kısaca; Detox ile uygulamanın temel kullanıcı akışını adım adım test edip, her şeyin beklendiği gibi çalıştığını doğrulayacağız. Hazırsan, başlayalım!

Detox Kurulum ve Yapılandırma

Bu Aşamada React Native projemize Detox ile uçtan uca test desteği ekleyeceğiz.
Adımları hem kod hem açıklamalarla ilerleteceğiz.

Test Ortamı Gereklilikleri

  • iOS: Güncel Xcode (genelde 13+), macOS ve iOS simülatör gerekiyor.
  • Android: Android Studio, uygun SDK (API 30+), ve çalışan AVD emülatörü şart.

1.Detox paketlerini kurma

# npm ile:
npm install --save-dev detox jest
# yarn ile:
yarn add --dev detox jest

2.e2e klasörü oluşturma

mkdir e2e

3.Jest yapılandırması

e2e/jest.config.js dosyası oluşturup aşağıdaki kodu ekleyin:

// e2e/jest.config.js
/** @type {import('@jest/types').Config.InitialOptions} */
module.exports = {
  rootDir: "..",
  testMatch: ["<rootDir>/e2e/**/*.test.js"],
  testTimeout: 120000,
  maxWorkers: 1,
  globalSetup: "detox/runners/jest/globalSetup",
  globalTeardown: "detox/runners/jest/globalTeardown",
  reporters: ["detox/runners/jest/reporter"],
  testEnvironment: "detox/runners/jest/testEnvironment",
  verbose: true,
};

Bu dosya Jest’in Detox ile birlikte nasıl çalışacağını ayarlıyor. Test konumu, zaman aşımı ve global ayarlar burada belirlenir.

4.Detox yapılandırması

.detoxrc.js dosyasını proje kök dizinine ekleyin:

// .detoxrc.jss
module.exports = {
  testRunner: {
    args: {
      $0: "jest",
      config: "e2e/jest.config.js",
    },
    jest: {
      setupTimeout: 120000,
    },
  },
  apps: {
    "ios.debug": {
      type: "ios.app",
      binaryPath:
        "ios/build/Build/Products/Debug-iphonesimulator/detoxdemo.app",
      build:
        "xcodebuild -workspace ios/detoxdemo.xcworkspace -scheme detoxdemo -configuration Debug -sdk iphonesimulator -derivedDataPath ios/build",
    },
    "android.debug": {
      type: "android.apk",
      binaryPath: "android/app/build/outputs/apk/debug/app-debug.apk",
      build:
        "cd android && ./gradlew assembleDebug assembleAndroidTest -DtestBuildType=debug",
      reversePorts: [8081],
    },
  },
  devices: {
    simulator: {
      type: "ios.simulator",
      device: { type: "iPhone 16 Plus" },
    },
    emulator: {
      type: "android.emulator",
      device: { avdName: "Pixel_3a_API_30_x86" },
    },
  },
  configurations: {
    "ios.sim.debug": {
      device: "simulator",
      app: "ios.debug",
      permissions: { notifications: "YES" },
    },
    "android.emu.debug": {
      device: "emulator",
      app: "android.debug",
    },
  },
};

Burada hangi cihazın kullanılacağı, uygulamanın nasıl build edileceği ve hangi test ortamında çalıştırılacağı ayarlanır.

Not Testleri çalıştırırken, dosya yolları veya uygulama adlarında yapılacak küçük hatalar testlerin başlamasını engeller. Özellikle iOS'ta, uygulama isminde özel karakterler varsa bunlar sorun yaratabilir. Bu nedenle build işleminden sonra dosya ve isimlerin doğruluğunu kontrol etmeyi unutmayın.

5.package.json komutlarını ekleme

//package.json
"scripts": {
  ...
  "detox-ios": "detox test --configuration ios.sim.debug",
  "detox-android": "detox test --configuration android.emu.debug",
  "detox-build-ios": "detox build --configuration ios.sim.debug",
  "detox-build-android": "detox build --configuration android.emu.debug"
}

Testleri kolayca çalıştırmak ve build almak için komutları package.json dosyasına ekliyoruz. Böylece karmaşık Detox komutlarını her seferinde elle yazmak zorunda kalmıyoruz.

✅ Kurulum tamam!

Artık e2e klasöründe test dosyaları oluşturup uygulamanızın uçtan uca testlerini yazabilirsiniz.

Detox Test Dosyamı Yazmaya Başlayalım

e2e klasörüne starter.test.js dosyamısı oluşturalım

mkdir e2e/starter.test.js

Bu dosya, uygulamanın temel login akışını, yeni kayıt ekleme işlemini
ve arama filtresi özelliğini test eder.

//e2e/starter.test.js
describe("Login Flow", () => {
  // ✅ Testlerden önce uygulamayı sıfırdan başlat
  beforeAll(async () => {
    await device.launchApp({ delete: true });
  });

  // 1️⃣ Geçerli bilgilerle giriş yapma testi
  it("should login with valid credentials", async () => {
    await element(by.id("username-input")).typeText("testuser"); // Kullanıcı adı gir
    await element(by.id("password-input")).typeText("123456"); // Şifre gir
    await element(by.id("remember-switch")).tap(); // "Beni hatırla" aktif et
    await element(by.id("login-title")).tap(); // Klavyeyi kapatmak için başlığa dokun
    await element(by.id("login-button")).tap(); // Giriş butonuna tıkla
    await expect(element(by.text("Kayıtlar"))).toBeVisible(); // Kayıtlar ekranına geçildi mi kontrol et
  });

  // 2️⃣ Yeni kayıt ekleme testi
  it("should go to add screen and add a new item", async () => {
    await element(by.id("go-add-button")).tap(); // "Yeni Ekle" butonuna bas
    await element(by.id("title-input")).typeText("Deneme Başlık"); // Başlık gir
    await element(by.id("description-input")).typeText("Bu bir açıklamadır"); // Açıklama gir
    await element(by.id("save-button")).tap(); // Kaydet butonuna bas
    await expect(element(by.text("Deneme Başlık"))).toBeVisible(); // Eklenen başlığı gör
    await expect(element(by.text("Genel"))).toBeVisible(); // Kategoriyi gör
  });

  // 3️⃣ Arama filtresi testi
  it("should filter the list by search text", async () => {
    await element(by.id("search-input")).typeText("Deneme"); // Arama kutusuna yaz
    await expect(element(by.text("Deneme Başlık"))).toBeVisible(); // Filtre sonucu gör
    await expect(element(by.text("Genel"))).toBeVisible(); // Kategori de görünsün
    await element(by.id("search-input")).replaceText(""); // Arama kutusunu temizle
  });
});

Daha fazla detay ve gelişmiş konfigürasyonlar için Detox’un resmi dokümantasyonuna göz atabilirsiniz:
https://wix.github.io/Detox/docs/config/overview Buradan ileri seviye ayarlar ve kullanım örneklerine ulaşabilirsiniz.

🚀 Şimdi Sahne Sırası Detox’ta!

Tamam… Kodları yazdık, testID’lerimizi ekledik, Detox ayarlarımızı yaptık.
Artık sahne ışıkları açık, seyirciler hazır: Testlerimiz sahneye çıkıyor! 🎭


🎯 Önce kostümlerini giydir (Build al)

Uygulamamız sahneye çıkmadan önce hazırlanmalı.
Yani önce build alıyoruz:

iOS için:

npm run detox-build-ios
# veya
yarn detox-build-ios

Android için:

npm run detox-build-android
# veya
yarn detox-build-android

🎬 Şimdi perde açılsın (Testleri çalıştır)

Derleme tamamlandıysa, işte o an geldi… Detox cihazınızı açacak, uygulamanızı başlatacak ve adım adım test senaryolarını oynayacak.

iOS testi:

npm run detox-ios
# veya
yarn detox-ios

Android testi:

npm run detox-android
# veya
yarn detox-android

Project Screenshot

Consol Çıktısı
burak@192 detox-demo % yarn detox-ios
yarn run v1.22.22
$ detox test --configuration ios.sim.debug
23:11:32.128 detox[63281] B jest --config e2e/jest.config.js
23:11:35.752 detox[63282] i starter.test.js is assigned to 5A243533-A563-4CE3-B1D3-D03BFA04929C (iPhone 16 Plus)
23:11:37.463 detox[63282] i com.fivori.detoxdemo launched. To watch simulator logs, run:
        /usr/bin/xcrun simctl spawn 5A243533-A563-4CE3-B1D3-D03BFA04929C log stream --level debug --style compact --predicate 'process == "detoxdemo"'
23:11:40.065 detox[63282] i Login Flow: should login with valid credentials
23:11:46.512 detox[63282] i Login Flow: should login with valid credentials [OK]
23:11:46.513 detox[63282] i Login Flow: should go to add screen and add a new item
23:11:53.908 detox[63282] i Login Flow: should go to add screen and add a new item [OK]
23:11:53.909 detox[63282] i Login Flow: should filter the list by search text
23:11:57.900 detox[63282] i Login Flow: should filter the list by search text [OK]

 PASS  e2e/starter.test.js (25.278 s)
  Login Flow
    ✓ should login with valid credentials (6446 ms)
    ✓ should go to add screen and add a new item (7394 ms)
    ✓ should filter the list by search text (3990 ms)

Test Suites: 1 passed, 1 total
Tests:       3 passed, 3 total
Snapshots:   0 total
Time:        25.334 s, estimated 26 s
Ran all test suites.
✨  Done in 26.21s.
burak@192 detox-demo %


Test yazmak bazen göz korkutucu olabilir, hele Detox gibi uçtan uca test araçlarıyla çalışmak ilk başta karmaşık görünebilir. Ama inanın bana, bu zorluklar her seferinde biraz daha güven ve başarıyla yer değiştiriyor.

Jest ile birim testler yazmak güzel bir başlangıç, ama Detox ile gerçek kullanıcı deneyimini yakalamak, uygulamanı bir üst seviyeye taşıyor. Bu süreç, sadece kodunun değil, senin de gelişimin için büyük bir adım.

Umarım bu rehber, senin test dünyasında sağlam adımlar atmana yardımcı olur. Unutma; test yazmak sadece hataları bulmak değil, daha kaliteli, sürdürülebilir ve güvenilir bir uygulama inşa etmek demek.

Yeni testler, yeni keşifler ve bol başarılar! Kodlamaya devam et, öğrenmeye devam et, hata yapmaktan korkma ve en önemlisi—bu yolculuğun tadını çıkar. 😊

Burak Sağlık

Burak Saglik

©2024 Desing and Developed by @Burak Sağlık

All rights reserved