1. Başlamadan önce
Cloud Firestore ve Cloud Functions gibi sunucusuz arka uç araçlarının kullanımı çok kolaydır ancak test edilmesi zor olabilir. Firebase Local Emulator Suite, uygulamanızı hızlı ve güvenli bir şekilde geliştirebilmeniz için bu hizmetlerin yerel sürümlerini geliştirme makinenizde çalıştırmanıza olanak tanır.
Ön koşullar
- Visual Studio Code, Atom veya Sublime Text gibi basit bir düzenleyici
- Node.js 10.0.0 veya sonraki sürümler (Node.js'yi yüklemek için nvm'yi kullanın, sürümünüzü kontrol etmek için
node --versionkomutunu çalıştırın) - Java 7 veya sonraki sürümler (Java'yı yüklemek için bu talimatları uygulayın, sürümünüzü kontrol etmek için
java -versionkomutunu çalıştırın)
Yapacaklarınız
Bu codelab'de, birden fazla Firebase hizmeti tarafından desteklenen basit bir online alışveriş uygulamasını çalıştırıp hata ayıklayacaksınız:
- Cloud Firestore: Gerçek zamanlı özelliklere sahip, dünya çapında ölçeklenebilir, sunucusuz bir NoSQL veritabanıdır.
- Cloud Functions: Etkinliklere veya HTTP isteklerine yanıt olarak çalışan sunucusuz bir arka uç kodudur.
- Firebase Authentication: Diğer Firebase ürünleriyle entegre olan yönetilen bir kimlik doğrulama hizmetidir.
- Firebase Hosting: Web uygulamaları için hızlı ve güvenli barındırma.
Yerel geliştirmeyi etkinleştirmek için uygulamayı Emulator Suite'e bağlayacaksınız.

Ayrıca şunları da öğreneceksiniz:
- Uygulamanızı Emulator Suite'e bağlama ve çeşitli emülatörlerin nasıl bağlandığı
- Firebase Güvenlik Kuralları'nın işleyiş şekli ve Firestore Güvenlik Kuralları'nı yerel bir emülatörde test etme
- Firestore etkinlikleri tarafından tetiklenen bir Firebase Functions yazma ve Emulator Suite'te çalışan entegrasyon testleri yazma
2. Kur
Kaynak kodunu alma
Bu codelab'de, The Fire Store örneğinin neredeyse tamamlanmış bir sürümüyle başlayacaksınız. Bu nedenle, yapmanız gereken ilk şey kaynak kodunu klonlamaktır:
$ git clone https://github.com/firebase/emulators-codelab.git
Ardından, bu codelab'in geri kalanında çalışacağınız codelab dizinine gidin:
$ cd emulators-codelab/codelab-initial-state
Şimdi, kodu çalıştırabilmek için bağımlılıkları yükleyin. İnternet bağlantınız yavaşsa bu işlem bir iki dakika sürebilir:
# Move into the functions directory
$ cd functions
# Install dependencies
$ npm install
# Move back into the previous directory
$ cd ../
Firebase CLI'yı edinme
Emulator Suite, aşağıdaki komutla makinenize yüklenebilen Firebase CLI'nin (komut satırı arayüzü) bir parçasıdır:
$ npm install -g firebase-tools
Ardından, CLI'nın en yeni sürümüne sahip olduğunuzu doğrulayın. Bu codelab, 9.0.0 veya sonraki sürümlerle çalışır ancak sonraki sürümlerde daha fazla hata düzeltmesi bulunur.
$ firebase --version 9.6.0
Firebase projenize bağlanma
Firebase projesi oluşturma
- Google Hesabınızı kullanarak Firebase konsolunda oturum açın.
- Yeni bir proje oluşturmak için düğmeyi tıklayın ve bir proje adı (ör.
Emulators Codelab) girin.
- Devam'ı tıklayın.
- İstenirse Firebase şartlarını inceleyip kabul edin ve ardından Devam'ı tıklayın.
- (İsteğe bağlı) Firebase konsolunda Yapay Zeka Desteği'ni ("Firebase'de Gemini" olarak adlandırılır) etkinleştirin.
- Bu codelab için Google Analytics'e ihtiyacınız yoktur. Bu nedenle Google Analytics seçeneğini devre dışı bırakın.
- Create project'i (Proje oluştur) tıklayın, projenizin temel hazırlığının tamamlanmasını bekleyin ve ardından Continue'yu (Devam) tıklayın.
Kodunuzu Firebase projenize bağlama
Şimdi bu kodu Firebase projenize bağlamamız gerekiyor. Öncelikle Firebase CLI'da oturum açmak için aşağıdaki komutu çalıştırın:
$ firebase login
Ardından, proje takma adı oluşturmak için aşağıdaki komutu çalıştırın. $YOUR_PROJECT_ID yerine Firebase projenizin kimliğini girin.
$ firebase use $YOUR_PROJECT_ID
Artık uygulamayı çalıştırmaya hazırsınız.
3. Emülatörleri çalıştırma
Bu bölümde, uygulamayı yerel olarak çalıştıracaksınız. Bu, Emulator Suite'i başlatma zamanının geldiği anlamına gelir.
Emülatörleri başlatma
Emülatörleri başlatmak için codelab kaynak dizininde aşağıdaki komutu çalıştırın:
$ firebase emulators:start --import=./seed
Aşağıdakine benzer bir çıkış görürsünüz:
$ firebase emulators:start --import=./seed i emulators: Starting emulators: auth, functions, firestore, hosting ⚠ functions: The following emulators are not running, calls to these services from the Functions emulator will affect production: database, pubsub i firestore: Importing data from /Users/samstern/Projects/emulators-codelab/codelab-initial-state/seed/firestore_export/firestore_export.overall_export_metadata i firestore: Firestore Emulator logging to firestore-debug.log i hosting: Serving hosting files from: public ✔ hosting: Local server: http://127.0.0.1:5000 i ui: Emulator UI logging to ui-debug.log i functions: Watching "/Users/samstern/Projects/emulators-codelab/codelab-initial-state/functions" for Cloud Functions... ✔ functions[calculateCart]: firestore function initialized. ┌─────────────────────────────────────────────────────────────┐ │ ✔ All emulators ready! It is now safe to connect your app. │ │ i View Emulator UI at http://127.0.0.1:4000 │ └─────────────────────────────────────────────────────────────┘ ┌────────────────┬────────────────┬─────────────────────────────────┐ │ Emulator │ Host:Port │ View in Emulator UI │ ├────────────────┼────────────────┼─────────────────────────────────┤ │ Authentication │ 127.0.0.1:9099 │ http://127.0.0.1:4000/auth │ ├────────────────┼────────────────┼─────────────────────────────────┤ │ Functions │ 127.0.0.1:5001 │ http://127.0.0.1:4000/functions │ ├────────────────┼────────────────┼─────────────────────────────────┤ │ Firestore │ 127.0.0.1:8080 │ http://127.0.0.1:4000/firestore │ ├────────────────┼────────────────┼─────────────────────────────────┤ │ Hosting │ 127.0.0.1:5000 │ n/a │ └────────────────┴────────────────┴─────────────────────────────────┘ Emulator Hub running at 127.0.0.1:4400 Other reserved ports: 4500 Issues? Report them at https://github.com/firebase/firebase-tools/issues and attach the *-debug.log files.
All emulators started (Tüm emülatörler başlatıldı) mesajını gördüğünüzde uygulama kullanıma hazır demektir.
Web uygulamasını emülatörlere bağlama
Günlüklerdeki tabloya göre Cloud Firestore emülatörünün 8080 numaralı bağlantı noktasını, Authentication emülatörünün ise 9099 numaralı bağlantı noktasını dinlediğini görebiliriz.
┌────────────────┬────────────────┬─────────────────────────────────┐ │ Emulator │ Host:Port │ View in Emulator UI │ ├────────────────┼────────────────┼─────────────────────────────────┤ │ Authentication │ 127.0.0.1:9099 │ http://127.0.0.1:4000/auth │ ├────────────────┼────────────────┼─────────────────────────────────┤ │ Functions │ 127.0.0.1:5001 │ http://127.0.0.1:4000/functions │ ├────────────────┼────────────────┼─────────────────────────────────┤ │ Firestore │ 127.0.0.1:8080 │ http://127.0.0.1:4000/firestore │ ├────────────────┼────────────────┼─────────────────────────────────┤ │ Hosting │ 127.0.0.1:5000 │ n/a │ └────────────────┴────────────────┴─────────────────────────────────┘
Şimdi ön uç kodunuzu üretim ortamı yerine emülatöre bağlayalım. public/js/homepage.js dosyasını açın ve onDocumentReady işlevini bulun. Kodun standart Firestore ve Auth örneklerine eriştiğini görebiliriz:
public/js/homepage.js
const auth = firebaseApp.auth();
const db = firebaseApp.firestore();
db ve auth nesnelerini yerel emülatörleri işaret edecek şekilde güncelleyelim:
public/js/homepage.js
const auth = firebaseApp.auth();
const db = firebaseApp.firestore();
// ADD THESE LINES
if (location.hostname === "127.0.0.1") {
console.log("127.0.0.1 detected!");
auth.useEmulator("http://127.0.0.1:9099");
db.useEmulator("127.0.0.1", 8080);
}
Uygulama yerel makinenizde çalışırken (Barındırma emülatörü tarafından sunulur) Firestore istemcisi de üretim veritabanı yerine yerel emülatörü işaret eder.
EmulatorUI'yi açma
Web tarayıcınızda http://127.0.0.1:4000/ adresine gidin. Emulator Suite kullanıcı arayüzünü görürsünüz.

Firestore Emulator'ın kullanıcı arayüzünü görmek için tıklayın. items koleksiyonu, --import Verileri İçe Aktarma nedeniyle zaten veri içeriyor.

4. Uygulamayı çalıştırma
Uygulamayı aç
Web tarayıcınızda http://127.0.0.1:5000 adresine gidin. Fire Store'un makinenizde yerel olarak çalıştığını göreceksiniz.

Uygulamayı kullanma
Ana sayfada bir ürün seçip Alışveriş sepetine ekle'yi tıklayın. Maalesef aşağıdaki hatayla karşılaşırsınız:

Bu hatayı düzeltelim. Her şey emülatörlerde çalıştığı için gerçek verileri etkileme endişesi olmadan denemeler yapabiliriz.
5. Uygulamada hata ayıklama
Hatayı bulma
Chrome geliştirici konsoluna bakalım. Konsoldaki hatayı görmek için Control+Shift+J (Windows, Linux, Chrome OS) veya Command+Option+J (Mac) tuşlarına basın:

addToCart yönteminde bir hata oluştu. Şimdi bu hatayı inceleyelim. Bu yöntemde uid adlı bir şeye nerede erişmeye çalışıyoruz ve neden null olabilir? Yöntem şu anda public/js/homepage.js içinde şu şekilde görünür:
public/js/homepage.js
addToCart(id, itemData) {
console.log("addToCart", id, JSON.stringify(itemData));
return this.db
.collection("carts")
.doc(this.auth.currentUser.uid)
.collection("items")
.doc(id)
.set(itemData);
}
Aha! Uygulamada oturum açmadık. Firebase Authentication belgelerine göre, oturum açmadığımızda auth.currentUser, null olur. Bunun için bir kontrol ekleyelim:
public/js/homepage.js
addToCart(id, itemData) {
// ADD THESE LINES
if (this.auth.currentUser === null) {
this.showError("You must be signed in!");
return;
}
// ...
}
Uygulamayı test etme
Şimdi sayfayı yenileyin ve Sepete Ekle'yi tıklayın. Bu kez daha anlaşılır bir hata mesajı alırsınız:

Ancak üstteki araç çubuğunda Oturum Aç'ı ve ardından tekrar Alışveriş Sepetine Ekle'yi tıklarsanız alışveriş sepetinin güncellendiğini görürsünüz.
Ancak sayılar hiç de doğru görünmüyor:

Endişelenmeyin, bu hatayı yakında düzelteceğiz. Öncelikle, alışveriş sepetinize bir ürün eklediğinizde gerçekte neler olduğuna ayrıntılı bir şekilde bakalım.
6. Yerel işlev tetikleyicileri
Alışveriş sepetine ekle'yi tıkladığınızda birden fazla emülatör içeren bir etkinlik zinciri başlar. Alışveriş sepetinize bir öğe ekledikten sonra Firebase CLI günlüklerinde aşağıdakine benzer mesajlar görürsünüz:
i functions: Beginning execution of "calculateCart" i functions: Finished "calculateCart" in ~1s
Bu günlükleri ve gözlemlediğiniz kullanıcı arayüzü güncellemesini oluşturmak için dört önemli etkinlik gerçekleşti:

1) Firestore Yazma - İstemci
Firestore koleksiyonuna (/carts/{cartId}/items/{itemId}/) yeni bir belge eklenir. Bu kodu public/js/homepage.js içindeki addToCart işlevinde görebilirsiniz:
public/js/homepage.js
addToCart(id, itemData) {
// ...
console.log("addToCart", id, JSON.stringify(itemData));
return this.db
.collection("carts")
.doc(this.auth.currentUser.uid)
.collection("items")
.doc(id)
.set(itemData);
}
2) Cloud Functions İşlevi Tetiklendi
Cloud Functions işlevi calculateCart, onWrite tetikleyicisini kullanarak alışveriş sepeti öğelerinde gerçekleşen tüm yazma etkinliklerini (oluşturma, güncelleme veya silme) dinler. Bu tetikleyiciyi functions/index.js bölümünde görebilirsiniz:
functions/index.js
exports.calculateCart = functions.firestore
.document("carts/{cartId}/items/{itemId}")
.onWrite(async (change, context) => {
try {
let totalPrice = 125.98;
let itemCount = 8;
const cartRef = db.collection("carts").doc(context.params.cartId);
await cartRef.update({
totalPrice,
itemCount
});
} catch(err) {
}
}
);
3) Firestore Write - Yönetici
calculateCart işlevi, alışveriş sepetindeki tüm öğeleri okur ve toplam miktar ile fiyatı toplar, ardından "sepet" dokümanını yeni toplamlarla günceller (yukarıdaki cartRef.update(...) bölümüne bakın).
4) Firestore Okuma - İstemci
Web ön ucu, alışveriş sepetindeki değişikliklerle ilgili güncellemeleri almak için abone olur. Cloud Functions işlevi yeni toplamları yazıp kullanıcı arayüzünü güncelledikten sonra public/js/homepage.js bölümünde görebileceğiniz gibi gerçek zamanlı bir güncelleme alır:
public/js/homepage.js
this.cartUnsub = cartRef.onSnapshot(cart => {
// The cart document was changed, update the UI
// ...
});
Özet
Bravo! Tamamen yerel testler için üç farklı Firebase emülatörü kullanan tamamen yerel bir uygulama oluşturdunuz.

Bir saniye, hepsi bu kadar değil! Bir sonraki bölümde şunları öğreneceksiniz:
- Firebase emülatörlerini kullanan birim testleri yazma
- Güvenlik kurallarınızda hata ayıklamak için Firebase emülatörlerini kullanma
7. Uygulamanıza özel güvenlik kuralları oluşturma
Web uygulamamız veri okuyup yazıyor ancak şu ana kadar güvenlik konusunda hiç endişelenmedik. Cloud Firestore, verileri okuma ve yazma erişimine kimlerin sahip olduğunu belirtmek için "Güvenlik Kuralları" adlı bir sistem kullanır. Emulator Suite, bu kuralların prototipini oluşturmak için ideal bir araçtır.
Düzenleyicide emulators-codelab/codelab-initial-state/firestore.rules dosyasını açın. Kurallarımızda üç ana bölüm olduğunu göreceksiniz:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// User's cart metadata
match /carts/{cartID} {
// TODO: Change these! Anyone can read or write.
allow read, write: if true;
}
// Items inside the user's cart
match /carts/{cartID}/items/{itemID} {
// TODO: Change these! Anyone can read or write.
allow read, write: if true;
}
// All items available in the store. Users can read
// items but never write them.
match /items/{itemID} {
allow read: if true;
}
}
}
Şu anda herkes veritabanımızdaki verileri okuyabilir ve veritabanımıza veri yazabilir. Yalnızca geçerli işlemlerin gerçekleştiğinden ve hassas bilgilerin sızdırılmadığından emin olmak istiyoruz.
Bu codelab'de, en az ayrıcalık ilkesini uygulayarak tüm dokümanları kilitleyecek ve tüm kullanıcılar ihtiyaç duydukları erişime sahip olana kadar kademeli olarak erişim ekleyeceğiz. İlk iki kuralı, koşulu false olarak ayarlayarak erişimi reddedecek şekilde güncelleyelim:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// User's cart metadata
match /carts/{cartID} {
// UPDATE THIS LINE
allow read, write: if false;
}
// Items inside the user's cart
match /carts/{cartID}/items/{itemID} {
// UPDATE THIS LINE
allow read, write: if false;
}
// All items available in the store. Users can read
// items but never write them.
match /items/{itemID} {
allow read: if true;
}
}
}
8. Emülatörleri ve testleri çalıştırma
Emülatörleri başlatma
Komut satırında emulators-codelab/codelab-initial-state/ dizininde olduğunuzdan emin olun. Önceki adımlardan kalan emülatörler hâlâ çalışıyor olabilir. Aksi takdirde emülatörleri tekrar başlatın:
$ firebase emulators:start --import=./seed
Emülatörler çalışmaya başladıktan sonra testleri yerel olarak çalıştırabilirsiniz.
Testleri çalıştırma
emulators-codelab/codelab-initial-state/ dizininden yeni bir terminal sekmesindeki komut satırında
Öncelikle functions dizinine gidin (codelab'in geri kalanında bu dizinde kalacağız):
$ cd functions
Şimdi functions dizininde mocha testlerini çalıştırın ve çıktının en üstüne gidin:
# Run the tests
$ npm test
> functions@ test .../emulators-codelab/codelab-initial-state/functions
> mocha
shopping carts
1) can be created and updated by the cart owner
2) can be read only by the cart owner
shopping cart items
3) can be read only by the cart owner
4) can be added only by the cart owner
adding an item to the cart recalculates the cart total.
- should sum the cost of their items
0 passing (364ms)
1 pending
4 failing
Şu anda dört hata var. Kural dosyasını oluştururken daha fazla testin başarılı olduğunu görerek ilerleme durumunu ölçebilirsiniz.
9. Alışveriş sepetine güvenli erişim
İlk iki hata, aşağıdakileri test eden "alışveriş sepeti" testleridir:
- Kullanıcılar yalnızca kendi alışveriş sepetlerini oluşturabilir ve güncelleyebilir.
- Kullanıcılar yalnızca kendi alışveriş sepetlerini okuyabilir.
functions/test.js
it('can be created and updated by the cart owner', async () => {
// Alice can create her own cart
await firebase.assertSucceeds(aliceDb.doc("carts/alicesCart").set({
ownerUID: "alice",
total: 0
}));
// Bob can't create Alice's cart
await firebase.assertFails(bobDb.doc("carts/alicesCart").set({
ownerUID: "alice",
total: 0
}));
// Alice can update her own cart with a new total
await firebase.assertSucceeds(aliceDb.doc("carts/alicesCart").update({
total: 1
}));
// Bob can't update Alice's cart with a new total
await firebase.assertFails(bobDb.doc("carts/alicesCart").update({
total: 1
}));
});
it("can be read only by the cart owner", async () => {
// Setup: Create Alice's cart as admin
await admin.doc("carts/alicesCart").set({
ownerUID: "alice",
total: 0
});
// Alice can read her own cart
await firebase.assertSucceeds(aliceDb.doc("carts/alicesCart").get());
// Bob can't read Alice's cart
await firebase.assertFails(bobDb.doc("carts/alicesCart").get());
});
Bu testleri geçelim. Düzenleyicide güvenlik kuralları dosyasını (firestore.rules) açın ve match /carts/{cartID} içindeki ifadeleri güncelleyin:
firestore.rules
rules_version = '2';
service cloud.firestore {
// UPDATE THESE LINES
match /carts/{cartID} {
allow create: if request.auth.uid == request.resource.data.ownerUID;
allow read, update, delete: if request.auth.uid == resource.data.ownerUID;
}
// ...
}
}
Bu kurallar artık yalnızca alışveriş sepeti sahibinin okuma ve yazma erişimine izin veriyor.
Gelen verileri ve kullanıcının kimlik doğrulamasını doğrulamak için her kural bağlamında kullanılabilen iki nesne kullanırız:
requestnesnesi, denenmekte olan işlemle ilgili verileri ve meta verileri içerir.- Bir Firebase projesi Firebase Authentication kullanıyorsa
request.authnesnesi, isteği gönderen kullanıcıyı tanımlar.
10. Alışveriş sepeti erişimini test etme
Emulator Suite, firestore.rules her kaydedildiğinde kuralları otomatik olarak günceller. Emülatörün kuralları güncellediğini, emülatörü çalıştıran sekmede Rules updated mesajını bularak doğrulayabilirsiniz:

Testleri yeniden çalıştırın ve ilk iki testin başarılı olduğunu kontrol edin:
$ npm test
> functions@ test .../emulators-codelab/codelab-initial-state/functions
> mocha
shopping carts
✓ can be created and updated by the cart owner (195ms)
✓ can be read only by the cart owner (136ms)
shopping cart items
1) can be read only by the cart owner
2) can be added only by the cart owner
adding an item to the cart recalculates the cart total.
- should sum the cost of their items
2 passing (482ms)
1 pending
2 failing
Tebrikler! Alışveriş sepetlerine erişiminiz güvence altına alındı. Başarısız olan bir sonraki teste geçelim.
11. Kullanıcı arayüzünde "Alışveriş sepetine ekle" akışını kontrol edin.
Şu anda, alışveriş sepeti sahipleri alışveriş sepetlerini okuyup yazabilse de alışveriş sepetlerindeki öğeleri tek tek okuyamaz veya yazamaz. Bunun nedeni, sahiplerin alışveriş sepeti dokümanına erişimi olsa da alışveriş sepetinin items alt koleksiyonuna erişiminin olmamasıdır.
Bu durum, kullanıcılar için sorunlara yol açar.
http://127.0.0.1:5000, üzerinde çalışan web kullanıcı arayüzüne dönün ve sepetinize bir şeyler eklemeyi deneyin. Kullanıcılara items alt koleksiyonunda oluşturulan belgelere erişim izni henüz verilmediği için hata ayıklama konsolunda görülebilen bir Permission Denied hatası alırsınız.
12. Alışveriş sepetindeki öğelere erişime izin verme
Bu iki test, kullanıcıların yalnızca kendi alışveriş sepetlerine ürün ekleyebildiğini veya kendi alışveriş sepetlerindeki ürünleri okuyabildiğini doğrular:
it("can be read only by the cart owner", async () => {
// Alice can read items in her own cart
await firebase.assertSucceeds(aliceDb.doc("carts/alicesCart/items/milk").get());
// Bob can't read items in alice's cart
await firebase.assertFails(bobDb.doc("carts/alicesCart/items/milk").get())
});
it("can be added only by the cart owner", async () => {
// Alice can add an item to her own cart
await firebase.assertSucceeds(aliceDb.doc("carts/alicesCart/items/lemon").set({
name: "lemon",
price: 0.99
}));
// Bob can't add an item to alice's cart
await firebase.assertFails(bobDb.doc("carts/alicesCart/items/lemon").set({
name: "lemon",
price: 0.99
}));
});
Bu nedenle, mevcut kullanıcının UID'si alışveriş sepeti dokümanındaki ownerUID ile aynıysa erişime izin veren bir kural yazabiliriz. create, update, delete için farklı kurallar belirtmeniz gerekmediğinden, verileri değiştiren tüm istekler için geçerli olan bir write kuralı kullanabilirsiniz.
items alt koleksiyonundaki belgeler için kuralı güncelleyin. Koşuldaki get, Firestore'dan bir değer okuyor. Bu örnekte, alışveriş sepeti dokümanındaki ownerUID okunuyor.
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// ...
// UPDATE THESE LINES
match /carts/{cartID}/items/{itemID} {
allow read, write: if get(/databases/$(database)/documents/carts/$(cartID)).data.ownerUID == request.auth.uid;
}
// ...
}
}
13. Alışveriş sepeti öğelerine erişimi test etme
Şimdi testi yeniden çalıştırabiliriz. Çıkışın en üstüne gidin ve daha fazla testin başarılı olduğunu kontrol edin:
$ npm test
> functions@ test .../emulators-codelab/codelab-initial-state/functions
> mocha
shopping carts
✓ can be created and updated by the cart owner (195ms)
✓ can be read only by the cart owner (136ms)
shopping cart items
✓ can be read only by the cart owner (111ms)
✓ can be added only by the cart owner
adding an item to the cart recalculates the cart total.
- should sum the cost of their items
4 passing (401ms)
1 pending
Güzel! Artık tüm testlerimiz başarılı. Bekleyen bir testimiz var ancak birkaç adım sonra bu teste geçeceğiz.
14. "Alışveriş sepetine ekleme" akışını tekrar kontrol edin
Web kullanıcı arabirimine (http://127.0.0.1:5000) dönün ve sepete bir öğe ekleyin. Bu, testlerimizin ve kurallarımızın istemcinin gerektirdiği işlevlerle eşleştiğini doğrulamak için önemli bir adımdır. (Kullanıcı arayüzünü en son denediğimizde kullanıcıların alışveriş sepetlerine ürün ekleyemediğini unutmayın.)

firestore.rules kaydedildiğinde istemci, kuralları otomatik olarak yeniden yükler. Alışveriş sepetine bir şey eklemeyi deneyin.
Özet
Bravo! Uygulamanızın güvenliğini artırdınız. Bu, uygulamanızı üretime hazır hale getirmek için önemli bir adımdır. Bu bir üretim uygulaması olsaydı bu testleri sürekli entegrasyon ardışık düzenimize ekleyebilirdik. Bu sayede, başkaları kuralları değiştirse bile alışveriş sepeti verilerimizin bu erişim denetimlerine sahip olacağından emin olabiliriz.

Ama dahası da var.
Devam ederseniz şunları öğreneceksiniz:
- Firestore etkinliği tarafından tetiklenen bir işlev yazma
- Birden fazla emülatörde çalışan testler oluşturma
15. Cloud Functions testlerini ayarlama
Şimdiye kadar web uygulamamızın ön ucuna ve Firestore Güvenlik Kuralları'na odaklandık. Ancak bu uygulama, kullanıcının alışveriş sepetini güncel tutmak için Cloud Functions'ı da kullanır. Bu nedenle, bu kodu da test etmek istiyoruz.
Emulator Suite, Cloud Functions işlevlerini test etmeyi çok kolaylaştırır. Cloud Firestore ve diğer hizmetleri kullanan işlevleri bile test edebilirsiniz.
Düzenleyicide emulators-codelab/codelab-initial-state/functions/test.js dosyasını açın ve dosyadaki son teste gidin. Şu anda beklemede olarak işaretlenmiş:
// REMOVE .skip FROM THIS LINE
describe.skip("adding an item to the cart recalculates the cart total. ", () => {
// ...
it("should sum the cost of their items", async () => {
...
});
});
Testi etkinleştirmek için .skip ifadesini kaldırın. Böylece şu şekilde görünür:
describe("adding an item to the cart recalculates the cart total. ", () => {
// ...
it("should sum the cost of their items", async () => {
...
});
});
Ardından, dosyanın üst kısmındaki REAL_FIREBASE_PROJECT_ID değişkenini bulun ve gerçek Firebase proje kimliğinizle değiştirin:
// CHANGE THIS LINE
const REAL_FIREBASE_PROJECT_ID = "changeme";
Proje kimliğinizi unuttuysanız Firebase konsolundaki Proje Ayarları'nda Firebase proje kimliğinizi bulabilirsiniz:

16. İşlev testlerini inceleme
Bu test, Cloud Firestore ile Cloud Functions arasındaki etkileşimi doğruladığı için önceki codelab'lerdeki testlerden daha fazla kurulum gerektirir. Bu testin üzerinden geçelim ve ne beklediği hakkında fikir edinelim.
Alışveriş sepeti oluşturma
Cloud Functions, güvenilir bir sunucu ortamında çalışır ve Admin SDK tarafından kullanılan hizmet hesabı kimlik doğrulamasını kullanabilir . Öncelikle, initializeApp yerine initializeAdminApp kullanarak bir uygulamayı başlatırsınız. Ardından, öğe ekleyeceğimiz alışveriş sepeti için bir DocumentReference oluşturup alışveriş sepetini ilk kullanıma hazırlarsınız:
it("should sum the cost of their items", async () => {
const db = firebase
.initializeAdminApp({ projectId: REAL_FIREBASE_PROJECT_ID })
.firestore();
// Setup: Initialize cart
const aliceCartRef = db.doc("carts/alice")
await aliceCartRef.set({ ownerUID: "alice", totalPrice: 0 });
...
});
İşlevi tetikleme
Ardından, işlevi tetiklemek için alışveriş sepeti dokümanımızın items alt koleksiyonuna doküman ekleyin. İşlevde gerçekleşen ekleme işlemini test ettiğinizden emin olmak için iki öğe ekleyin.
it("should sum the cost of their items", async () => {
const db = firebase
.initializeAdminApp({ projectId: REAL_FIREBASE_PROJECT_ID })
.firestore();
// Setup: Initialize cart
const aliceCartRef = db.doc("carts/alice")
await aliceCartRef.set({ ownerUID: "alice", totalPrice: 0 });
// Trigger calculateCart by adding items to the cart
const aliceItemsRef = aliceCartRef.collection("items");
await aliceItemsRef.doc("doc1").set({name: "nectarine", price: 2.99});
await aliceItemsRef.doc("doc2").set({ name: "grapefruit", price: 6.99 });
...
});
});
Test beklentilerini belirleme
Alışveriş sepeti dokümanındaki değişiklikleri dinleyecek bir dinleyici kaydetmek için onSnapshot() kullanın. onSnapshot(), dinleyicinin kaydını iptal etmek için çağırabileceğiniz bir işlev döndürür.
Bu test için toplam fiyatı 9, 98 ABD doları olan iki öğe ekleyin. Ardından, alışveriş sepetinde beklenen itemCount ve totalPrice değerlerinin olup olmadığını kontrol edin. Öyleyse işlev görevini yerine getirmiştir.
it("should sum the cost of their items", (done) => {
const db = firebase
.initializeAdminApp({ projectId: REAL_FIREBASE_PROJECT_ID })
.firestore();
// Setup: Initialize cart
const aliceCartRef = db.doc("carts/alice")
aliceCartRef.set({ ownerUID: "alice", totalPrice: 0 });
// Trigger calculateCart by adding items to the cart
const aliceItemsRef = aliceCartRef.collection("items");
aliceItemsRef.doc("doc1").set({name: "nectarine", price: 2.99});
aliceItemsRef.doc("doc2").set({ name: "grapefruit", price: 6.99 });
// Listen for every update to the cart. Every time an item is added to
// the cart's subcollection of items, the function updates `totalPrice`
// and `itemCount` attributes on the cart.
// Returns a function that can be called to unsubscribe the listener.
await new Promise((resolve) => {
const unsubscribe = aliceCartRef.onSnapshot(snap => {
// If the function worked, these will be cart's final attributes.
const expectedCount = 2;
const expectedTotal = 9.98;
// When the `itemCount`and `totalPrice` match the expectations for the
// two items added, the promise resolves, and the test passes.
if (snap.data().itemCount === expectedCount && snap.data().totalPrice == expectedTotal) {
// Call the function returned by `onSnapshot` to unsubscribe from updates
unsubscribe();
resolve();
};
});
});
});
});
17. Testleri çalıştırma
Önceki testlerden kalan emülatörler hâlâ çalışıyor olabilir. Çalışmıyorsa emülatörleri başlatın. Komut satırından şu komutu çalıştırın:
$ firebase emulators:start --import=./seed
Yeni bir terminal sekmesi açın (emülatörleri çalışır durumda bırakın) ve functions dizinine gidin. Güvenlik kuralları testlerinden bu sekme hâlâ açık olabilir.
$ cd functions
Şimdi birim testlerini çalıştırın. Toplam 5 test görmeniz gerekir:
$ npm test
> functions@ test .../emulators-codelab/codelab-initial-state/functions
> mocha
shopping cart creation
✓ can be created by the cart owner (82ms)
shopping cart reads, updates, and deletes
✓ cart can be read by the cart owner (42ms)
shopping cart items
✓ items can be read by the cart owner (40ms)
✓ items can be added by the cart owner
adding an item to the cart recalculates the cart total.
1) should sum the cost of their items
4 passing (2s)
1 failing
Belirli bir hataya bakarsanız bunun bir zaman aşımı hatası olduğu görülür. Bunun nedeni, testin işlevin doğru şekilde güncellenmesini beklemesi ancak bu güncellemenin hiçbir zaman gerçekleşmemesidir. Artık testi karşılayacak işlevi yazmaya hazırız.
18. İşlev yazma
Bu testi düzeltmek için functions/index.js içindeki işlevi güncellemeniz gerekir. Bu işlevin bir kısmı yazılmış olsa da tamamlanmamış. İşlev şu anda aşağıdaki gibi görünür:
// Recalculates the total cost of a cart; triggered when there's a change
// to any items in a cart.
exports.calculateCart = functions
.firestore.document("carts/{cartId}/items/{itemId}")
.onWrite(async (change, context) => {
console.log(`onWrite: ${change.after.ref.path}`);
if (!change.after.exists) {
// Ignore deletes
return;
}
let totalPrice = 125.98;
let itemCount = 8;
try {
const cartRef = db.collection("carts").doc(context.params.cartId);
await cartRef.update({
totalPrice,
itemCount
});
} catch(err) {
}
});
İşlev, alışveriş sepeti referansını doğru şekilde ayarlıyor ancak totalPrice ve itemCount değerlerini hesaplamak yerine bunları sabit kodlu değerlerle güncelliyor.
'i getirme ve yineleme
items alt koleksiyonu
items alt koleksiyonu olacak yeni bir sabit (itemsSnap) başlatın. Ardından, koleksiyondaki tüm dokümanları iterasyonla işleyin.
// Recalculates the total cost of a cart; triggered when there's a change
// to any items in a cart.
exports.calculateCart = functions
.firestore.document("carts/{cartId}/items/{itemId}")
.onWrite(async (change, context) => {
console.log(`onWrite: ${change.after.ref.path}`);
if (!change.after.exists) {
// Ignore deletes
return;
}
try {
let totalPrice = 125.98;
let itemCount = 8;
const cartRef = db.collection("carts").doc(context.params.cartId);
// ADD LINES FROM HERE
const itemsSnap = await cartRef.collection("items").get();
itemsSnap.docs.forEach(item => {
const itemData = item.data();
})
// TO HERE
return cartRef.update({
totalPrice,
itemCount
});
} catch(err) {
}
});
totalPrice ve itemCount değerlerini hesaplama
İlk olarak totalPrice ve itemCount değerlerini sıfıra ayarlayalım.
Ardından, yineleme bloğumuza mantığı ekleyin. Öncelikle öğenin fiyatı olup olmadığını kontrol edin. Öğenin miktarı belirtilmemişse varsayılan olarak 1 değerini kullanın. Ardından, miktarı itemCount olan toplam tutara ekleyin. Son olarak, öğenin fiyatını miktarla çarpıp totalPrice olan toplam tutara ekleyin:
// Recalculates the total cost of a cart; triggered when there's a change
// to any items in a cart.
exports.calculateCart = functions
.firestore.document("carts/{cartId}/items/{itemId}")
.onWrite(async (change, context) => {
console.log(`onWrite: ${change.after.ref.path}`);
if (!change.after.exists) {
// Ignore deletes
return;
}
try {
// CHANGE THESE LINES
let totalPrice = 0;
let itemCount = 0;
const cartRef = db.collection("carts").doc(context.params.cartId);
const itemsSnap = await cartRef.collection("items").get();
itemsSnap.docs.forEach(item => {
const itemData = item.data();
// ADD LINES FROM HERE
if (itemData.price) {
// If not specified, the quantity is 1
const quantity = itemData.quantity ? itemData.quantity : 1;
itemCount += quantity;
totalPrice += (itemData.price * quantity);
}
// TO HERE
})
await cartRef.update({
totalPrice,
itemCount
});
} catch(err) {
}
});
Başarılı ve hata durumlarında hata ayıklamaya yardımcı olması için günlük kaydı da ekleyebilirsiniz:
// Recalculates the total cost of a cart; triggered when there's a change
// to any items in a cart.
exports.calculateCart = functions
.firestore.document("carts/{cartId}/items/{itemId}")
.onWrite(async (change, context) => {
console.log(`onWrite: ${change.after.ref.path}`);
if (!change.after.exists) {
// Ignore deletes
return;
}
let totalPrice = 0;
let itemCount = 0;
try {
const cartRef = db.collection("carts").doc(context.params.cartId);
const itemsSnap = await cartRef.collection("items").get();
itemsSnap.docs.forEach(item => {
const itemData = item.data();
if (itemData.price) {
// If not specified, the quantity is 1
const quantity = (itemData.quantity) ? itemData.quantity : 1;
itemCount += quantity;
totalPrice += (itemData.price * quantity);
}
});
await cartRef.update({
totalPrice,
itemCount
});
// OPTIONAL LOGGING HERE
console.log("Cart total successfully recalculated: ", totalPrice);
} catch(err) {
// OPTIONAL LOGGING HERE
console.warn("update error", err);
}
});
19. Testleri yeniden çalıştırma
Komut satırında emülatörlerin hâlâ çalıştığından emin olun ve testleri yeniden çalıştırın. İşlevlerdeki değişiklikleri otomatik olarak algıladıkları için emülatörleri yeniden başlatmanız gerekmez. Tüm testlerin başarılı olduğunu göreceksiniz:
$ npm test
> functions@ test .../emulators-codelab/codelab-initial-state/functions
> mocha
shopping cart creation
✓ can be created by the cart owner (306ms)
shopping cart reads, updates, and deletes
✓ cart can be read by the cart owner (59ms)
shopping cart items
✓ items can be read by the cart owner
✓ items can be added by the cart owner
adding an item to the cart recalculates the cart total.
✓ should sum the cost of their items (800ms)
5 passing (1s)
Tebrikler!
20. Vitrin kullanıcı arayüzünü kullanarak deneme
Son test için web uygulamasına (http://127.0.0.1:5000/) dönün ve sepete bir öğe ekleyin.

Alışveriş sepetinin doğru toplam tutarla güncellendiğini onaylayın. Çok teşekkür ederim.
Özet
Cloud Functions for Firebase ile Cloud Firestore arasındaki karmaşık bir test durumunu incelediniz. Testin başarılı olması için bir Cloud Functions işlevi yazdınız. Ayrıca yeni işlevin kullanıcı arayüzünde çalıştığını da doğruladınız. Tüm bunları yerel olarak, emülatörleri kendi makinenizde çalıştırarak yaptınız.
Ayrıca yerel emülatörlerde çalışan bir web istemcisi oluşturdunuz, verileri korumak için güvenlik kurallarını özelleştirdiniz ve yerel emülatörleri kullanarak güvenlik kurallarını test ettiniz.
