Angular Projesini Jest İle Test Etmek
Neden Jest Çatısını Kullanmalıyız?
Özetle aşağıdaki sebeplerden dolayı Jest çatısını kullanmalıyız:
- Hissedilebilir derecede hızlı ve bunu birazda testleri paralel çalıştırabilmesine borçlu
- Testler Karma’dan farklı olarak bir internet gezgini olmadan çalıştırılabilir
- Anlık görüntü testiyle grafik arayüzün beklenmedik şekilde değişmediğinden emin olunur
- Zengin komut satırı özellikleriylle sadece başarısız testleri, dosya adı veya test adlarına göre süzme
- Anlaşılır hata mesajları
- Okunabilir test raporları
- Korumalı alan testleri
- Kod kapsamını (code coverage) yapılandırabilme
- DOM’u soyutlamak için jsdom kütüphanesi kullandığı için Sürekli Entegrasyon sunucularında ayrıca bir araç gerekmez
- Babel ve Typescript ile sorunsuz çalışır
Jest Nedir?
Facebook tarafından geliştirilmiş Jasmine üstüne kurulu (API’si neredeyse aynı yapıda) bir test çatısıdır. Birincil olarak react uygulamaları düşünülerek büyük küçük tüm takımların kullanımı için tasarlanmıştır.
Angular Projenize Jest Kurulumu Yapmak
Karma ile ilgili paketlerin neler olduğuna bakalım:
- Karma : Testleri koşturacak esas çatı
- karma-chrome-launcher : Google Chrome, Google Chrome Canary ve Google Chromium için başlatıcı
- karma-coverage-istanbul-reporter : Testlerin kodun ne kadarını kapsadığını gösteren raporlayıcı
- karma-jasmine-html-reporter : Test sonuçlarını raporlayan paket
- karma-jasmine : BDD temelli Jasmine test çatısının Karma adaptörü
Öncelikle Karma ile ilintili paketleri kaldıralım:
$ npm remove karma karma-chrome-launcher karma-coverage-istanbul-reporter karma-jasmine karma-jasmine-html-reporter$ rm ./karma.conf.js ./src/test.ts
Aşağıdaki paketlere ihtiyacımız var:
- jest : Karma yerine testlerimizi çalıştıracağımız çatımız
- @angular-builders/jest : Genel ayarları bizim için yapacak kütüphane
- @types/jest : Typescript kodlar ve hata ayıklarken kolaylık olması için “Jest typings”
$ npm install -D \
jest \
@angular-builders/jest \
@types/jest
@types Paket Ayarları
Önce @types/… Nedir?
Bir npm paketinin adıyla, “@types/…” paketi, typescript sözdizimine uygun bir nevi arayüz dosyasıdır. tsconfig.json İçinde types isimli dizininin içine “@types” kapsamındaki paketin adını ekliyoruz ki typescript compiler javascript dosyalarını anlamak yerine d.ts dosyasının içine bakarak doğru sözdizimi ile yazılıp yazılmadığını anlasın. Biz types dizisine paket adlarını yazdığımızda compiler typeRoots dizisinde belirtilen dizinlere bakarak ilgili paketleri bulmaya çalışır.
Typescript betik dilinde geliştirdiğimiz testlerimizi derlemek için kullanacağımız derleyici ayarlarını tuttuğumuz dosya: tsconfig.spec.json
Karma ile Jasmine testlerini koşturuyorduk, bu yüzden tsconfig.spec.json içinde tipleri getirmek için jasmine vardı şimdi ise jest ile değiştireceğiz.
tsconfig.spec.json Dosyasını ayar dosyası olarak alıp, hangi dosyaları derleyeceğimize baktığımızda:
Derlenecek dosyalar = files + include - exclude
files içindeki test.ts dosyası Karma tarafından kullanılan bir dosya olduğu için hem files dizisinden hem de dosyayı dizinden siliyoruz.
Typescript bize dekoratörler ile geliyor. C# kullanırken attribute, Java da annotation adıyla geçen dekoratörleri jest çatısı çözümlemekte zorlandığı için almanız muhtemel hatayı önlemek adına testler için kullandığınız typescript ayar dosyasına
"emitDecoratorMetadata": true
özelliğini eklemelisiniz
Uygulamayı gerçek ortam için ayaklandırdığımızda tsconfig.json dosyasındaki ayarlar kullanılır. Uçtan uca (end-2-end) testler ise gerçek ortam için uygulamanın ayağa kaldırılmasıyla çalıştırılır. Uçtan uca testler için Protractor kullanılıtken, Protractor ise jasmine kullanıyor. Kullandığımız IDE’nin bize testler için Jasmine önermesini istemediğimizden tsconfig.json içinde de @types/jasmine paketini kullanmadığımızı belirtmeliyiz.
IDE’nizin uçtan uca testlerinizde size Jest türlerini önermesi için types dizisinde jasmine yerine jest yazıyoruz.
“ng test” Ayarı
Angular CLI bize ng serve, ng test, ng build gibi komutlar sunarak işlerimizi kolaylaştırıyor.
ng test Komutu altında çalışan ise angular.json içinde test özelliğinin altındadır. Varsayılan olarak Karma test entegrasyonuyla geldiği için @angular-devkit/build-angular paketinde builders.json içindeki karma ayarlarına göre test süreci başlaltılacaktır.
Bu durumu jest ile değiştirmek için şu değişikliği yapacağız:
Referanslar:
- http://facebook.github.io/jest/docs/api.html
- https://medium.com/angular-in-depth/angular-cli-ng-test-with-jest-in-3-minutes-v2-1060ddd7908d
Jest Ayarlar
Özetle
Projenizde olacak tüm modüllerin (uygulama projesi olduğunda kök dizininde src dizininde, kütüphaneler ise projects dizininde) angular.json dosyasında ayarları yapılır.
Jest ayarlarını 3 yerde yapabiliriz:
- package.json
- angular.json içinde testin ayarlandığı kısımda options özelliğinde
- komut satırında
- ayar
- Her projenin tsConfig ve config
- Jest için yapılacak genel ayarları jest.config.js dosyasında tutacağız.
Detaylarıyla
ng test dediğimizde arka planda jest çalıştırılacak. O halde — showConfig diyerek jestin ayarlarını görelim.
$ ng test --showConfig
Özel bir ayar dosyası olmadığını söylüyor.
- Bu ayarlar nereden geliyor?
- Ayarların anlamları neler?
Öncelikle typescript dosyalarımızı javascript’e transpile ediyor olmamız gerekiyor ki jest test çatısı sayesinde testlerimizi koşturabilelim. Jest’in javascript dosyalarını işlemek gibi bir özelliği olduğunu bu cümleden anlıyoruz. O halde typescript dosyalarını bizim için dönüştürmek adına (elbette typescript derleyici kullanılacak ama) ts-jest paketini kullanacağımızı belirtelim.
ts-jest
ts-jest, Jest için kaynak haritası (source map) desteğine sahip bir TypeScript ön işlemcisidir ve bu da TypeScript ile yazılmış projeleri test etmek için Jest’i kullanmanızı sağlar. Tür denetimi de dahil olmak üzere TypeScript’in tüm özelliklerini destekler.
Varsayılan olarak Jest herhangi bir yapılandırma dosyası olmadan çalışabilir, ancak .ts dosyalarını derlemez. TypeScript’i ts-jest ile javascript’e dönüştürmek ve derlemek için bir yapılandırma dosyası oluşturmamız gerekecek.
$ npm install --save-dev
jest
typescript
ts-jest
@types/jest
Unutmayın; sadece ts-jest kurmak isteseydik bağımlılıkları olan typescript ve jest kütüphanelerini de yüklememiz gerekecekti. Çünkü ts-jest, yalnızca .ts ve .tsx dosyalarıyla ilgilenir ve JavaScript dosyalarını olduğu gibi bırakır.
ts-jest için 3 farklı ayar varsayılan olarak geliyor:
// jest.config.js
module.exports = {
// [...]
// varsayılan ayarlar yukarıdaki gibi 3 adet idi.
// ts-jest
// ts-jest/presets/js-with-ts
// ts-jest/presets/js-with-babel
// sadece ts kodları için 1'inci, javascript ile birlikte ts kodları için 2'inci
preset: 'ts-jest'
};
Jest ayarlarını 3 farklı ortamda tutabiliyorduk:
- config dosyası
- package.json
- komut satırı
Varsayılan ayarların haricindeki ayarları ts-jest için de 3 farklı yerde tutabiliyoruz:
- jest.config.js
- package.json
- tsconfig.json (ts-jest, tsconfig’den Jest yapılandırma biçimine dönüştürmek için config dosyasının .js sürümüne ihtiyaç duyar. Yani package.json içinde ayarları tutarsak tsconfig’den jest ayarlarına geçiş yapamayız)
Bundan sonra tüm ayarları jest.config.js üstünde göstereceğim.
ts-jest Ayar Dosyası
Ayar dosyasını değiştirmeden jest’in ayarlarını görelim
C:\Temp\tsjest>npx jest --showConfig
{
"configs": [
{
"automock": false,
"cache": true,
"cacheDirectory": "C:\\Users\\cem.topkaya\\AppData\\Local\\Temp\\jest",
"clearMocks": false,
"coveragePathIgnorePatterns": [
"\\\\node_modules\\\\"
],
"cwd": "C:\\Temp\\tsjest",
"detectLeaks": false,
"detectOpenHandles": false,
"errorOnDeprecated": false,
"extraGlobals": [],
"forceCoverageMatch": [],
"globals": {},
"haste": {
"computeSha1": false,
"throwOnModuleCollision": false
},
"moduleDirectories": [
"node_modules"
],
"moduleFileExtensions": [
"js",
"json",
"jsx",
"ts",
"tsx",
"node"
],
"moduleNameMapper": [],
"modulePathIgnorePatterns": [],
"name": "51130dbad29e4e124f746099e77b60df",
"prettierPath": "prettier",
"resetMocks": false,
"resetModules": false,
"restoreMocks": false,
"rootDir": "C:\\Temp\\tsjest",
"roots": [
"C:\\Temp\\tsjest"
],
"runner": "jest-runner",
"setupFiles": [],
"setupFilesAfterEnv": [],
"skipFilter": false,
"snapshotSerializers": [],
"testEnvironment": "C:\\Temp\\tsjest\\node_modules\\jest-environment-node\\build\\index.js",
"testEnvironmentOptions": {},
"testLocationInResults": false,
"testMatch": [
"**/__tests__/**/*.[jt]s?(x)",
"**/?(*.)+(spec|test).[tj]s?(x)"
],
"testPathIgnorePatterns": [
"\\\\node_modules\\\\"
],
"testRegex": [],
"testRunner": "C:\\Temp\\tsjest\\node_modules\\jest-jasmine2\\build\\index.js",
"testURL": "http://localhost",
"timers": "real",
"transform": [
[
"^.+\\.tsx?$",
"C:\\Temp\\tsjest\\node_modules\\ts-jest\\dist\\index.js",
{}
]
],
"transformIgnorePatterns": [
"\\\\node_modules\\\\"
],
"watchPathIgnorePatterns": []
}
],
"globalConfig": {
"bail": 0,
"changedFilesWithAncestor": false,
"collectCoverage": false,
"collectCoverageFrom": [],
"coverageDirectory": "C:\\Temp\\tsjest\\coverage",
"coverageProvider": "babel",
"coverageReporters": [
"json",
"text",
"lcov",
"clover"
],
"detectLeaks": false,
"detectOpenHandles": false,
"errorOnDeprecated": false,
"expand": false,
"findRelatedTests": false,
"forceExit": false,
"json": false,
"lastCommit": false,
"listTests": false,
"logHeapUsage": false,
"maxConcurrency": 5,
"maxWorkers": 7,
"noStackTrace": false,
"nonFlagArgs": [],
"notify": false,
"notifyMode": "failure-change",
"onlyChanged": false,
"onlyFailures": false,
"passWithNoTests": false,
"projects": [],
"rootDir": "C:\\Temp\\tsjest",
"runTestsByPath": false,
"skipFilter": false,
"testFailureExitCode": 1,
"testPathPattern": "",
"testSequencer": "C:\\Temp\\tsjest\\node_modules\\@jest\\test-sequencer\\build\\index.js",
"updateSnapshot": "new",
"useStderr": false,
"watch": false,
"watchAll": false,
"watchman": true
},
"version": "26.0.1"
}
Satır 18 de globals: { } jest’in varsayılan ayarlarında boş geliyor.
Yukarıdaki ekran görüntüsünde olduğu gibi eğer ts-jest vasıtasıyla bir jest.config.js dosyası yaratırsak
$ npx ts-jest config:init
Ve içine aşağıdaki gibi “globals” özelliğini eklersek, tekrar jest’in ayarlarını görmek istediğimizde kompozisyona yereldeki ayarlar eklendiğini görebiliriz.
$ npx jest --showConfig | awk '/"globals/,/\}/'
configPath
Jest ayarlarını ayrıca bir dosyada vermek istersek, bu dosya adı projenin dizininde jest.config.js adıyla kaydedilmiş olursa jest otomatik bulacaktır bu dosyayı. Ancak dosya eğer farklı bir isimde kaydedilmiş olursa ve farklı bir dizinde bulunuyorsa, jest’e configPath özelliğiyle ya komut satırından ya angular.json dosyasında ilgili projenin test ayarlarının yapıldığı yerde verilmeli. Aşağıdaki ekran görüntüsünde hem uygulamanın hem kütüphanenin jest ayarlarını yaptığımız dosyanın yolunu “options” içinde verdiğimizi görebilirsiniz.
tsConfig
ts-jest otomatik olarak proje kök dizininde tsconfig.json dosyasını arayacak bulursa içinde tanımlı derleme seçeneklerini (compilerOptions) kullanacak, bulamazsa varsayılan ayarları kullanacaktır.
// jest.config.js
module.exports = {
// [...]
globals: {
'ts-jest': {
tsConfig: {
importHelpers: true
// https://www.typescriptlang.org/docs/handbook/compiler-options.html#compiler-options
}
}
}
};
Eğer tsConfig değerine false ataması yaparsak tsconfig.json dosyasını aramayacak ve varsayılan typescript derleme seçeneklerini kullanacaktır.
// jest.config.js
module.exports = {
// [...]
globals: {
'ts-jest': {
tsConfig: false
}
}
};
Biz typescript derleme ayarlarını tsconfig.json ve bu dosyayı miras alan tsconfig.spec.json dosyalarında değiştirdiğimiz için tsConfig özelliğine her proje için ilgili typescript configuration bilgilerini alacağı json dosyasının yolunu göstereceğiz. Zaten kütüphane projeleri de en üstte projenin tsconfig.json dosyasını miras aldığı ve değerleri ezdikleri için projelerin kendi tsconfig.(spec.)json dosyalarını angular.json dosyasında belirteceğiz.
moduleNameMapper Ayarı
Projeniz modüllerden oluşuyor ve bunları test etmek istiyorsunuz. Derleyicinin bu modüllerin yerlerini bilmesi için tsconfig.json dosyamızdaki baseUrl veya paths bilgilerini kullandığını ve aşağıdaki gibi tsconfig.json içinde konumlandığını görelim. Derinlemesine basUrl ve paths için dalmadan önce modül kavramını hatırlayalım.
Modül
ECMAScript 2015'ten başlayarak JavaScript içinde bir modül konsepti vardır ve bu kavramı TypeScript de kullanır.
Modüller, küresel kapsamda değil, kendi kapsamlarında yürütülür; bu, bir modülde bildirilen değişkenlerin, işlevlerin, sınıfların vb. dışa aktarma formlarından biri kullanılarak açıkça dışa aktarılmadığı sürece, modülün dışında görünmeyeceği anlamına gelir. Tersine, farklı bir modülden dışa aktarılan bir değişken, fonksiyon, sınıf, arayüz vb. yapıları kullanmak için içe aktarma formlarından biri kullanılması gerekir. Kısaca modül içindeki bir yapıyı export etmezsen başka bir modülde import edemezsin!
Bir modülü içeriye aktarabilmek için modül yükleyicisi kullanmak zorundayız. Modül yükleyicisinin esas görevi içeri aktarılacak modülün dosya bağlantılarını çözümlemektir. Nodejs içinde kullanılan modül yükleyicisi CommonJS iken web uygulamalarından AMD tipindeki modülleri yükleyen modül yükleyicisi ise RequireJs’dir.
Modül yükleme sistemleri:
- Node.js (CommonJS),
- require.js (AMD),
- UMD,
- SystemJS,
- ECMAScript 2015 native modules (ES6)Typescript bir modülü çözümlemek için klasik veya Nodejs yöntemlerinden birini kullanacaktır.
Göreceli Modül Arayışında Klasik & Nodejs & Typescript Yöntemleri
/root/src/folder/A.ts dosyasındayız ve import { b } from “./moduleB” ile moduleB modülünden b yi içe aktarmak isteyelim. Klasik yöntemde göreceli modül arayışına (/, ./, ../ ile dizin yolunu göreceli olarak gireriz) girecek.
Klasik yöntemle Modülün bulunması şu şekilde olacaktır:
/root/src/folder/moduleB.ts
/root/src/folder/moduleB.d.ts
Nodejs için göreceli modül arayışı aşağıdaki akışta olacaktır:
/root/src/moduleB.js
eğer bulabilirse uzantısı .js olacak şekilde arar.- Dosya olarak bulamazsa modül adını dizin adı gibi arar:
/root/src/moduleB
eğer dizin adıyla bulursa içinde ÖNCEpackage.json
dosyası var mı? package.json varsa içinde"main"
özelliği var mı ( { "main": "lib/mainModule.js" } ) ? - Baktı ki dizin var ama package.json yok veya istediğimiz main tanımı yoksa bu kez dizin içinde index.js ( /root/src/moduleB/
index.js
) dosyası var mı diye bakar?
Typescript için ise göreceli modül arayışı şu şekilde gerçekleşecektir:
/root/src/moduleB.ts
/root/src/moduleB.tsx
/root/src/moduleB.d.ts
/root/src/moduleB/package.json
(Eğer"types"
adında bir özelliği varsa package.json işe yarayacaktır)/root/src/moduleB/index.ts
/root/src/moduleB/index.tsx
/root/src/moduleB/index.d.ts
Göreceli Olmayan Modül Arayışında Klasik & Nodejs & Typescript Yöntemleri
/root/src/folder/A.ts dosyasındayız ve import { b } from “moduleB” ile moduleB modülünden b yi içe aktarmak isteyelim. Göreceli bakışı tanımlayan / ./ ../ önekleri olmaksızın doğrudan moduleB diye arıyor olalım.
Klasik yöntemle:
/root/src/folder/moduleB.ts
/root/src/folder/moduleB.d.ts
/root/src/moduleB.ts
/root/src/moduleB.d.ts
/root/moduleB.ts
/root/moduleB.d.ts
/moduleB.ts
/moduleB.d.ts
Nodejs ile ararken node_modules dizinini önce A.ts dosyasının olduğu dizinden başlayarak üst dizinlere bakar ve arar:
/root/src/node_modules/moduleB.js
/root/src/node_modules/moduleB/package.json
(Eğer"main"
adında özelliği varsa)/root/src/node_modules/moduleB/index.js
/root/node_modules/moduleB.js
/root/node_modules/moduleB/package.json
(Eğer"main"
adında özelliği varsa)/root/node_modules/moduleB/index.js
/node_modules/moduleB.js
/node_modules/moduleB/package.json
(Eğer"main"
adında özelliği varsa)/node_modules/moduleB/index.js
Typescript ise nodejs gibi ancak bu kez sadece js dosyalarını değil .ts, .tsx, .d.ts dosyalarıyla birlikte package.json içinde types özelliğine bakarak arar:
/root/src/node_modules/moduleB.ts
/root/src/node_modules/moduleB.tsx
/root/src/node_modules/moduleB.d.ts
/root/src/node_modules/moduleB/package.json
(Eğer"types"
adında özelliği varsa)/root/src/node_modules/@types/moduleB.d.ts
/root/src/node_modules/moduleB/index.ts
/root/src/node_modules/moduleB/index.tsx
/root/src/node_modules/moduleB/index.d.ts
/root/node_modules/moduleB.ts
/root/node_modules/moduleB.tsx
/root/node_modules/moduleB.d.ts
/root/node_modules/moduleB/package.json
(Eğer"types"
adında özelliği varsa)/root/node_modules/@types/moduleB.d.ts
/root/node_modules/moduleB/index.ts
/root/node_modules/moduleB/index.tsx
/root/node_modules/moduleB/index.d.ts
/node_modules/moduleB.ts
/node_modules/moduleB.tsx
/node_modules/moduleB.d.ts
/node_modules/moduleB/package.json
(Eğer"types"
adında özelliği varsa)/node_modules/@types/moduleB.d.ts
/node_modules/moduleB/index.ts
/node_modules/moduleB/index.tsx
/node_modules/moduleB/index.d.ts
baseUrl
Modül kavramını daha iyi anladığımıza göre Typescript ile bir modülü derlediğimizde compiler için yardımcı tsconfig.json parametrelerini tanıyalım. Bunlar paths ve baseUrl özellikleridir. Kestirmeden diyebiliriz ki; paths özelliği kullanılıyorsa baseUrl muhakkak kullanılacak. Typescript projesindeki dosyaların aynı dizin yapısında derlenmiş çıktıları oluşturulmayabilir. Yani girdiler ile çıktılar farklı dizinlerde olabilirler. baseUrl Sayesinde girdilerin olduğu göreceli dizinin yolu tarif edilir. AMD modül sisteminde girdiler tek bir dosya olacak şekilde çıktı oluşturulurken girdi dosyaların görece konumları baseUrl sayesinde belirtilmiş olur. Yani baseUrl ayarı derleyiciye modülleri nerede bulacağını bildirir. Göreli olmayan adlara sahip tüm modül içe aktarmalarının baseUrl ile göreceli olduğu varsayılır.
paths
Bazen modüller doğrudan baseUrl altına yerleştirilmez. Örneğin, “jquery” modülüne aktarım çalışma zamanında “node_modules/jquery/dist/jquery.slim.min.js” ye çevrilir. Yükleyiciler, modül adlarını çalışma zamanında dosyalara eşlemek için Modül kavramında anlatıldığı gibi bir eşleme yapılandırması kullanır. TypeScript derleyicisi tsconfig.json dosyalarındaki “paths” özelliğini kullanarak bu tür eşlemelerin bildirilmesini destekler. Aşağıda, jquery için “paths” özelliğinin nasıl belirtileceğine ilişkin bir örnek verilmiştir.
{
"compilerOptions": {
"baseUrl": ".", // "paths" tanımlandığı için "baseUrl" tanımlı olmak zorundadır
"paths": {
"jquery": ["node_modules/jquery/dist/jquery"] // Bu eşleme "baseUrl" de belirtilen dizin yoluna görece yapılır
}
}
}
Lütfen “paths” bilgisinin “baseUrl” ile ilişkili olarak çözüldüğüne dikkat edin.
tsconfig.json Dosyasında “baseUrl” ifadesini “.” dışında bir değere atarken, eşlemeler buna göre değiştirilmelidir.
Diyelim, yukarıdaki örnekte “baseUrl”: “./src” ayarını yaptınız, sonra jquery “../node_modules/jquery/dist/jquery” olarak eşlenmelidir.
Tekrar ederek angular projemizdeki tsconfig.json yapısını inceleyelim. Typescript modülleri bulmak için baseUrl ile göreceli olarak dosyaları arar. Bazen modüller doğrudan baseUrl ile belirtilen bir yerde olmayabilir ve bu modüllerin yerlerini paths ile vermemiz gerekebilir.
Projeler dizininde çeşitli kütüphane tanımları oluşturulmuş durumda. Ayrıca src dizini altında tüm kütüphanelerin testini yapabileceğimiz bir application tipinde proje de mevcut. Application projesinde bu kütüphanelere referans verilmiş ve projenin ayağa kalktığında bu modüllere erişebilmesi için derleyicinin bulabilmesi gerekmekte. Bu yüzden paths altında @cinar/cn-nef adında bir modüle bağlantı yapıldığında “dist/cinar/cn-nef” dizininde ilgili modülü bulabileceğini bu şekilde belirtebiliyoruz.
Aynı mantıkta @ngx-translate/* adında tüm modüllerin “./node_modules/@ngx-translate” dizini içerisinde erişilebileceğini de derleyiciye bildiriyoruz. Kendi modüllerimiz bu sayede derlendiği sırada eğer @ngx-translate modüllerine erişmek isterse Typescript dizin yolunu bulduğu için özel modüllerimizin derlenmesinde bir sorun olmayacak.
allowJs Ayarı
TypeScript ve JavaScript dosyaları (.ts, .tsx, .js ve .jsx) ts-jest tarafından işlenir. tsconfig.json dosyanızda “allowJs”: true olarak ayarlamanız gerekir.
"globals": {
"ts-jest": {
"tsConfig": "C:\\Temp\\jesttest\\tsconfig.spec.json",
"stringifyContentPathRegex": "\\.html$",
"astTransformers": [
"jest-preset-angular/build/InlineFilesTransformer",
"jest-preset-angular/build/StripStylesTransformer",
"jest-preset-angular/build/InlineFilesTransformer",
"jest-preset-angular/build/StripStylesTransformer"
]
}
},
ts-jest yapılandırması Jest yapılandırma nesnesi içinde yapılır. Bu, jest özelliği altında package.json içinde veya kendi jest.config.js dosyasında olabilir. İkincisi daha özelleştirilebilir olduğu için tercih edilir, ancak ihtiyaçlarınıza ve tercihinize bağlıdır.
Ön ayarlar Jest’in varsayılan ayarlarında yaptığı gibi testMatch kullanır. Yapılandırmanızda bunun yerine testRegex kullanmak istiyorsanız testMatch öğesini null olarak ayarlamanız gerekir, aksi takdirde Jest’e bırakmış oluruz.
Kütüphane Modüllerinde Ayar Üstünlükleri
Ayar üstünlükleri CSS deki önceliklere benzer yapıdadır (etiket, satır içi stil, harici ve dahili stillerin işlenme sıraları gibi).
Angular projemizde jest ayarlarını da aynı şekilde sıralayabiliriz:
- angular.json içindeki ayar en baskını
- daha sonra kütüphane modülünün içindeki jest.config.js dosyasındaki ayarlar
- projenin kökünde yer alan jest.config.js ayarları
- package.json içindeki ayarlar
angular.json Dosyası içinde jest ayarlarını her kütüphane modülü için architect/test içinde yapılandırabiliriz:
ve JavaScript dosyaları (.ts, .tsx, .js ve .jsx) ts-jest tarafından işlenir. tsconfig.json dosyanızda “allowJs”: true olarak ayarlamanız gerekir.
// jest.config.js
module.exports = {
// [...]
globals: {
'ts-jest': {
tsConfig: false
}
}
};
Hata Giderme
TypeError: Cannot read property 'getComponentFromError' of null
Aşağıdaki hata angular projesinde koştuğu için
import 'jest-preset-angular';
satırını içeren setupJest.ts
(ismi farklı olabilir elbette) dosyasını her test öncesinde çalıştıracak.
● Servis içeren Servis › should be created TypeError: Cannot read property 'getComponentFromError' of null at TestBedViewEngine._initIfNeeded (../../packages/core/testing/src/test_bed.ts:378:46)
at TestBedViewEngine.get (../../packages/core/testing/src/test_bed.ts:464:10)
at Function.TestBedViewEngine.get (../../packages/core/testing/src/test_bed.ts:228:36)
at Object.<anonymous> (projects/kapsam/paket-a/src/lib/servis_test/servis-iceren.service.spec.ts:18:23)
TypeError: Cannot read property 'getComponentFromError' of null
hatasını alıyorsanız ve
her testten önce çalışmasını istediğimiz bir script varsa setupFilesAfterEnv
tam bu iş için biçilmiş kaftan.
Eğer ayarlar içinde yukarıdaki kodu açarsak, setupFilesAfterEnv:['<rootDir>/setupJest.ts'],
aşağıdaki komut satırını konsolda çalıştırıp şu ayarları görebiliriz:
$ ng test @kapsam/paket-a --showConfig | grep setupFilesAfterEnv -A3
"setupFilesAfterEnv": [
"C:\\Users\\cem.topkaya\\git\\jest-ng-test\\node_modules\\@angular-builders\\jest\\dist\\jest-config\\setup.js",
"C:\\Users\\cem.topkaya\\git\\jest-ng-test\\projects\\kapsam\\paket-a\\src\\setupJest.ts"
],
Yani başlangıçta çalışmasını istediğimiz dosyamız setupJest.ts dosyası listeye eklenmiş.
Aynı dosyayı bu kez dizi değil ama string olacak şekilde angular.json içinde aşağıdaki gibi tanımlarsak:
"test": {
"builder": "@angular-builders/jest:run",
"options": {
"setupFilesAfterEnv":"<rootDir>/setupJest.ts",
"configPath":"./jest.config.js",
"tsConfig": "tsconfig.spec.json"
}
}$ ng test @kapsam/paket-a --showConfig | grep setupFilesAfterEnv -A3
"setupFilesAfterEnv": [
"C:\\Users\\cem.topkaya\\git\\jest-ng-test\\projects\\kapsam\\paket-a\\src\\setupJest.ts"
],
Can't resolve all parameters for ServisIcerenService: (?).
Hatası
tsconfig.spec.json
"emitDecoratorMetadata": true,
jest.config.js içinde typescript derleme özelliklerini set etmek için işaret edeceğimiz tsconfig.xxx.json dosyasına @NgModule, @Component, @Injectable gibi dekorasyon için kullandığımız metadata tutucularını transpile edebilmesi, yukarıdaki kodu eklememişsek mümkündür. Eğer başlıktaki hatayı alıyorsak bu eklemeyi yapmadığımız için olabilir.
Ayrıca setupFilesAfterEnv ile her test çalıştırılmadan önce çalışmasını isteyeceğimiz script dosyalarını işaret ediyorduk. Bu başlangıç dosyalarından en azından birinde angular için import “jest-preset-angular”; kodunu içeren bir xxxx.ts dosyasını vermiş olmalıyız (setupJest.ts dosyası genel yaklaşımdır).
setupFilesAfterEnv ayarını yaptınız ancak yine Can't resolve all parameters for ServisIcerenService: (?).
hatasını alıyorsunuz.
● Servis içeren Servis › should be created Can't resolve all parameters for ServisIcerenService: (?). at syntaxError (../packages/compiler/src/util.ts:100:17)
at CompileMetadataResolver._getDependenciesMetadata (../packages/compiler/src/metadata_resolver.ts:957:27)
at CompileMetadataResolver._getTypeMetadata (../packages/compiler/src/metadata_resolver.ts:836:20)
at CompileMetadataResolver._getInjectableTypeMetadata (../packages/compiler/src/metadata_resolver.ts:1089:17)
at CompileMetadataResolver.getProviderMetadata (../packages/compiler/src/metadata_resolver.ts:1100:16)
at ../packages/compiler/src/metadata_resolver.ts:1021:38
at Array.forEach (<anonymous>)
at CompileMetadataResolver._getProvidersMetadata (../packages/compiler/src/metadata_resolver.ts:981:15)
at CompileMetadataResolver.getNgModuleMetadata (../packages/compiler/src/metadata_resolver.ts:649:30)
at JitCompiler._loadModules (../packages/compiler/src/jit/compiler.ts:127:49)
at JitCompiler._compileModuleAndAllComponents (../packages/compiler/src/jit/compiler.ts:115:32)
at JitCompiler.compileModuleAndAllComponentsSync (../packages/compiler/src/jit/compiler.ts:65:38)
at CompilerImpl.compileModuleAndAllComponentsSync (../packages/platform-browser-dynamic/src/compiler_factory.ts:61:35)
at TestingCompilerImpl.compileModuleAndAllComponentsSync (../../packages/platform-browser-dynamic/testing/src/compiler_factory.ts:52:27)
at TestBedViewEngine._initIfNeeded (../../packages/core/testing/src/test_bed.ts:376:28)
at TestBedViewEngine.get (../../packages/core/testing/src/test_bed.ts:464:10)
at Function.TestBedViewEngine.get (../../packages/core/testing/src/test_bed.ts:228:36)
at projects/kapsam/paket-a/src/lib/servis_test/servis-iceren.service.spec.ts:18:23
Bu sorunu gidermek için jest.config.js
dosyanıza preset: 'jest-preset-angular',
satırını aşağıdaki gibi ekleyin:
module.exports = {
preset: 'jest-preset-angular',
globals: {
"ts-jest": {
Jest ile Test
Diğer Jest kurulumlarından farklı olarak angular cli ile ng test komutunun çalışmaya devam edebilmesi için tüm ayarları yaptık.
Jest ile Kod Kapsamı (Code Coverage)
Kapsam raporu için istanbul paketi sayesinde oluşturabiliriz ancak başka paketler olmakla birlikte Jest çatısı içinde istanbul varsayılan olarak geliyor.
Ayrıca istanbul paketi de deprecate oldu:
Jest ayarlarının package.json, ayrı bir config dosyası veya komut satırında verilebildiğini hatırlayarak devam edelim.
Kapsam raporu oluşturmak için komut satırında test komutuna — coverage argümanı ekleyebiliriz.
$ ng test --coverage
Komut satırında eklenebilir olmasa bile performansı büyük derecede etkileyen “collectCoverage”: true özelliğini package.json içinde tutabliriz:
//package.json
{
...
"jest": {
"collectCoverage": true
}
}
Jest Araçları
Jest Runner
Uzantının ayarlarında jest.config.js dosyasının adı ve yeri farklıysa vermeniz gerekiyor. Bu ayarları proje bazında uygulayabilmek için Workspace’i seçiyoruz.
Workspace temelinde yapılan ayarlar .vscode dizini içinde yer alacaktır:
Eğer testi koşturacağımız projenin jest ayar dosyasını settings.json dosyasında da güncellemezsek, testimiz çalışmayacaktır (global bir ayar yapmadıysanız).
Debug
Jest
Varsayılan olarak jest.config.js veya jest.config.json dosyalarını arayacaktır. Eğer farklı isimde ayar dosya isimleriniz varsa tıpkı Jest Runner gibi ayarlarında belirtmeniz gerekir.
Angular ayarları
Hata ve Uyarılara Karşı Aksiyomlar
ts-jest[versions] (WARN) Version 3.5.3 of typescript installed has not been tested with ts-jest. If you're experiencing issues, consider using a supported version (>=3.8.0 <4.0.0-0). Please do not report issues in ts-jest if you are using unsupported versions.
3.8.0 versiyonundan büyük en yakın kararlı sürüm 3.8.2 olduğunu npm view typescript versions komutuyla görüyoruz ve yüklüyoruz:
$ npm i -D typescript@3.8.2
Aşağıdaki uyarı mesajını görürseniz:
ts-jest[config] (WARN) message TS151001: If you have issues related to imports, you should consider setting `esModuleInterop` to `true` in your TypeScript configuration file (usually `tsconfig.json`). See https://blogs.msdn.microsoft.com/typescript/2018/01/31/announcing-typescript-2-7/#easier-ecmascript-module-interoperability for more information.
Çözümü
tsconfig.json içinde compilerOptions nesnesine “esModuleInterop”: true özelliğini eklemelisiniz.
Detaylı Anlatımı
ECMAScript modülleri ES2015'te standartlaştırılmadan önce, JavaScript ekosisteminin farklı şekillerde çalışan birkaç farklı modül biçimi vardı. Standart geçtikten sonra, akıllarda mevcut “eski” modül formatlarıyla en iyi nasıl birlikte çalışılacağı sorusu kaldı.
TypeScript ve Babel farklı yaklaşımlar benimsedi ve şimdi bile gerçekten kilitli bir standart yok. Kısa öykü, Babel, Webpack veya Native React kullandıysanız ve alışık olduğunuzdan farklı import davranışları beklediyseniz, sizin için — esModuleInterop adlı yeni bir derleyici seçeneği var.
Babel ve Webpack, kullanıcıların CommonJS modüllerini varsayılan içe aktarma olarak içe aktarmalarına izin verir (import redis from ‘redis’; redis.connect() ), ancak bir ad alanındaki özellikleri tek tek içe aktarımına da imkan sağlar (tabi modül bir __esModule bayrağıyla işaretlenmedikçe) (import { connect } from redis;’ gibi).
TypeScript’in davranışı farklı olduğundan, kullanıcıların tür içeri alma (import) özelliğini kullanabilmeleri için TypeScript 1.8'den itibaren — allowSyntheticDefaultImports bayrağı eklendi.
Genel olarak, TypeScript’in CommonJS (ve AMD) modüllerine ilişkin görünümü, ad alanı içe aktarmalarının her zaman bir CommonJS modülü nesnesinin şekline karşılık gelmesi ve varsayılan bir içe aktarma işleminin bu modülde varsayılan adlı bir üyeye karşılık gelmesidir. Bu varsayım altında, adlandırılmış bir içe aktarma oluşturabilirsiniz
import { range } from "lodash";
for (let i of range(10)) {
// ...
}import * as express from "express";
// Should be an error in any valid implementation.
let app = express();
Kullanıcılara Babel veya Webpack ile aynı çalışma zamanı davranışı sağlamak için, eski modül biçimleri için TypeScript, — esModuleInterop bayrağı sağlar. CommonJS modülleri için aşağıdaki gibi varsayılan içe aktarmalar şöyle içeri aktarılır:
import express from "express";
let app = express();
CommonJS modüllerini ES6 modüllerinin içine aktarabilmek (import) için — esModuleInterop bayrağından önce typescript dosyamızda import * as moment from ‘moment’ kullanıyorduk. Bu typescript kodu Javascript’e transpile olduğunuda const moment = require(“moment”); şeklini alıyordu. Artık esModuleInterop bayrağıyla typescript dosyamızda import moment from ‘moment’ kullanabiliyoruz.
jest.base.config.js
console.log("---------------- ROOT Jest.base.config ---------------");
const { pathsToModuleNameMapper } = require("ts-jest/utils");
const { compilerOptions } = require("./tsconfig");
module.exports = {
globals: {
"ts-jest": {
allowSyntheticDefaultImports: true,
tsConfig: "tsconfig.spec.json",
},
},
transform: {
'^.+\\.ts?$': 'ts-jest',
'\\.html$': 'ts-jest'
},
// preset: "jest-preset-angular",
roots: ["<rootDir>/"],
// transformIgnorePatterns: [`<rootDir>/node_modules/(?!${esModules})`],
testMatch: ["**/+(*.)+(spec).(ts)?(x)"],
// setupFilesAfterEnv: ['<rootDir>/src/test.ts'],
//********************************************************* COVERAGE */
collectCoverage: true,
coverageReporters: ["html"],
coverageDirectory: "coverage/my-app",
coveragePathIgnorePatterns: [
"node_modules",
"test-config",
"<rootDir>/src/app/interfaces",
"jestGlobalMocks.ts",
".module.ts",
"<rootDir>/src/app/main.ts"
],
//*********************************************************** REPORT */
reporters: ['default'],
/**
Aşağıdaki hatayı çözmek için bir yeri ignore etmemiz gerekiyor
The name `@cinar/cn-nssf-api` was looked up in the Haste module map. It cannot be resolved, because there exists several different files, or packages, that provide a module for that particular name and platform. The platform is generic (no extension). You must delete or blacklist files until there remains only one of these:
* `C:\Users\cem.topkaya\git\gui_nef_nrf_nssf\dist\cinar\cn-nssf-api\package.json` (package)
* `C:\Users\cem.topkaya\git\gui_nef_nrf_nssf\projects\cinar\cn-nssf-api\package.json` (package)
*/
modulePathIgnorePatterns: ["<rootDir>/dist/"],
modulePaths: ["<rootDir>/node_modules"],
moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths || {}, {
prefix: "<rootDir>/",
}),
};
// console.log(">>>>>>> ",compilerOptions)
// console.log(">>>>>>> ",module.exports.moduleNameMapper)
console.log("-----------------/ROOT Jest.base.config---------------");
projects/cinar/cn-nssf/jest.config.js
console.log("---------------- NSSF Jest.config ---------------");
// const { pathsToModuleNameMapper } = require("ts-jest/utils");
// const { compilerOptions } = require("./tsconfig");
const baseConfig = require('../../../jest.base.config');
module.exports = {
...baseConfig,
// setupFilesAfterEnv: ['<rootDir>/../../../jest.base.setup.ts'],
roots: ["<rootDir>/projects/cinar/cn-nssf/src"],
setupFiles: ["<rootDir>/projects/cinar/cn-nssf/jest.helpers.js"],
modulePaths: ["<rootDir>/dist"],
// moduleNameMapper: {
// "@cinar/cn-nssf/(.*)": "<rootDir>/projects/cinar/cn-nssf/src/$1",
// "@cinar/cn-nssf-api/(.*)": "<rootDir>/dist/cinar/cn-nssf-api/$1"
// },
testMatch: ["**/cinar/cn-nssf/**/*.spec.ts"]
};
// console.log(">>>>>>> ",compilerOptions)
console.log(">>>>>>> ",module.exports.moduleNameMapper)
console.log("-----------------/NSSF Jest.config---------------");
projects/cinar/cn-nssf/tsconfig.spec.ts
{
"extends": "../../../tsconfig.json",
"compilerOptions": {
"outDir": "../../../out-tsc/spec",
"types": [
"jest",
"node"
],
"allowJs": false
},
"files": [
"src/test.ts"
],
"include": [
"**/*.spec.ts",
"**/*.d.ts"
]
}
tsconfig.json
{
"compileOnSave": false,
"compilerOptions": {
"baseUrl": "./",
"outDir": "./dist/out-tsc",
"sourceMap": true,
"declaration": false,
"downlevelIteration": true,
"experimentalDecorators": true,
"module": "esnext",
"moduleResolution": "node",
"importHelpers": true,
"target": "es2015",
"resolveJsonModule": true,
"esModuleInterop": true,
"typeRoots": [ "node_modules/@types" ],
"lib": [ "es2018", "dom" ],
"paths": {
"@cinar/cn-nrf": [ "dist/cinar/cn-nrf" ],
"@cinar/cn-nrf/*": [ "dist/cinar/cn-nrf/*" ],
"@ngx-translate/*": [ "./node_modules/@ngx-translate/*" ],
"@angular/*": [ "./node_modules/@angular/*" ],
"@cinar/cn-nef": [ "dist/cinar/cn-nef" ],
"@cinar/cn-nef/*": [ "dist/cinar/cn-nef/*" ],
"@cinar/cn-nssf": [ "dist/cinar/cn-nssf" ],
"@cinar/cn-nssf/*": [ "dist/cinar/cn-nssf/*" ],
"@cinar/cn-main": [ "dist/cinar/cn-main" ],
"@cinar/cn-main/*": [ "dist/cinar/cn-main/*" ],
"@cinar/cn-network": [ "dist/cinar/cn-network" ],
"@cinar/cn-network/*": [ "dist/cinar/cn-network/*" ],
"@cinar/cn-nrf-api": [ "dist/cinar/cn-nrf-api" ],
"@cinar/cn-nrf-api/*": [ "dist/cinar/cn-nrf-api/*" ],
"@cinar/cn-nssf-api": [ "dist/cinar/cn-nssf-api" ],
"@cinar/cn-nssf-api/*": [ "dist/cinar/cn-nssf-api/*" ],
"@cinar/gui-shared": [ "dist/cinar/gui-shared" ],
"@cinar/gui-shared/*": [ "dist/cinar/gui-shared/*" ],
"@cinar/cn-ims": [ "dist/cinar/cn-ims" ],
"@cinar/cn-ims/*": [ "dist/cinar/cn-ims/*" ]
},
"types": ["jasmine"]
},
"angularCompilerOptions": {
"fullTemplateTypeCheck": true,
"strictInjectionParameters": true,
"preserveSymlinks": true
}
}
tsconfig.spec.json
{
"extends": "./tsconfig.json",
"compilerOptions": {
"allowJs": true,
"esModuleInterop": true,
"emitDecoratorMetadata": true,
"outDir": "./out-tsc/spec",
"module": "commonjs",
"types": [
"jest",
"node"
]
},
"files": [
"src/polyfills.ts"
],
"include": [
"src/**/*.spec.ts",
"src/**/*.d.ts"
],
"exclude": [
"dist/**/*",
"node_modules/**/*"
]
}