/tech/~~~
)を配列で取得することをゴールとしており、Jest を用いたテスト駆動開発も行います。参考になれば幸いです!// src/lib/ga.ts import { GaxiosResponse } from 'gaxios'; import { google, analyticsreporting_v4 } from 'googleapis'; // JWT 認証 const client = new google.auth.JWT({ email: process.env.GA_CLIENT_EMAIL, key: process.env.GA_PRIVATE_KEY?.replace(/\\n/g, '\n'), scopes: ['https://www.googleapis.com/auth/analytics.readonly'], }); const analyticsreporting = google.analyticsreporting({ version: 'v4', auth: client, }); interface Args { startDate: '1daysAgo' | '7daysAgo'; filtersExpressions: Array<string>; pageSize: number; } export async function fetchPostGaData({ startDate, filtersExpressions, pageSize = 1000, }: Args): Promise<GaxiosResponse<analyticsreporting_v4.Schema$GetReportsResponse>> { const params = { requestBody: { reportRequests: [ { viewId: process.env.GA_VIEW_ID, dimensions: [ { name: 'ga:pagePath', }, ], metrics: [ { expression: 'ga:pageviews', }, ], orderBys: [ { fieldName: 'ga:pageviews', sortOrder: 'DESCENDING', }, ], dateRanges: [ { startDate: startDate, endDate: '1daysAgo', }, ], pageSize: pageSize, dimensionFilterClauses: [ { filters: [ { dimensionName: 'ga:pagePath', operator: 'REGEXP', expressions: filtersExpressions, }, ], }, ], }, ], }, }; return await analyticsreporting.reports.batchGet(params); } export async function fetchPopularPosts( pageSize: number, ): Promise<GaxiosResponse<analyticsreporting_v4.Schema$GetReportsResponse>> { const res = await fetchPostGaData({ startDate: '7daysAgo', // 期間は過去 1 週間 filtersExpressions: ['^/(blog|tech|news)/[0-9a-zA-Z\\-]+$'], // /blog/~~~, /tech/~~~, /news/~~~ のみ抽出 pageSize: pageSize, // 取得最大数 }); return res; } // 人気記事のパスを取得する関数 export async function getPopularPaths(pageSize = 1000): Promise<string[]> { const { reports } = (await fetchPopularPosts(pageSize)).data; // GA の行データを取得 const gaRowData = reports ? reports[0]?.data?.rows ?? [] : []; // パス取得 const popularPaths = gaRowData .filter((row) => row.dimensions && row.dimensions[0]) // dimensions や dimensions[0] が存在するデータのみ抽出 .map((row) => { return row.dimensions![0]; }); return popularPaths; }
.env
で管理した状態で API の疎通テストstatus
が 200
であることです。条件を踏まえ、テストコードを記述すると、以下のようになります。// __test__/tech/ga.test.tsx import { fetchPostGaData } from 'lib/ga'; describe('人気記事の取得', () => { test('Google Analytics の API 疎通を確認', async () => { const { status } = await fetchPostGaData(); expect(status).toBe(200); }); });
fetchPostGaData
関数は以下のように仮で定義します。// src/lib/ga.tsx export async function fetchPostGaData() { const res = null; return await res; }
# npm の場合 npm test ga.test.tsx # yarn の場合 yarn test ga.test.tsx # 実行結果 FAIL __test__/tech/ga.test.tsx ● Test suite failed to run __test__/tech/ga.test.tsx:4:11 - error TS2339: Property 'status' does not exist on type 'null'. 4 const { status } = await fetchPostGaData(); ~~~~~~ Test Suites: 1 failed, 1 total Tests: 0 total Snapshots: 0 total Time: 7.613 s, estimated 9 s Ran all test suites matching /ga.test.tsx/i.
fetchPostGaData
関数を実装します。全体のコードは以下の通りです。コメントで解説しています。// src/lib/ga.tsx import { GaxiosResponse } from 'gaxios'; import { google, analyticsreporting_v4 } from 'googleapis'; // 鍵の情報 const key: { type: string; project_id: string; private_key_id: string; private_key: string; client_email: string; client_id: string; auth_uri: string; token_uri: string; auth_provider_x509_cert_url: string; client_x509_cert_url: string; } = require('./kye.json'); // JWT 認証 // client_email と private_key のみ必要 const client = new google.auth.JWT({ email: key.client_email, key: key.private_key, scopes: ['https://www.googleapis.com/auth/analytics.readonly'], }); const analyticsreporting = google.analyticsreporting({ version: 'v4', auth: client, }); export async function fetchPostGaData(): Promise< GaxiosResponse<analyticsreporting_v4.Schema$GetReportsResponse> > { const params = { requestBody: { reportRequests: [ { // Google Analytics のビュー ID viewId: '~~~~~~~~', dimensions: [ { name: 'ga:pagePath', }, ], // 取得したい metrics // 今回は PV 数のみ取得 metrics: [ { expression: 'ga:pageviews', }, ], // 並び順 // 今回は PV を基準に、降順で取得 orderBys: [ { fieldName: 'ga:pageviews', sortOrder: 'DESCENDING', }, ], // 期間 dateRanges: [ { startDate: '1daysAgo', endDate: '1daysAgo', }, ], // ページの最大取得数 pageSize: 3, // パスのフィルタリング dimensionFilterClauses: [ { filters: [ { dimensionName: 'ga:pagePath', operator: 'REGEXP', expressions: ['/test'], }, ], }, ], }, ], }, }; return await analyticsreporting.reports.batchGet(params); }
# npm の場合 npm test ga.test.tsx # yarn の場合 yarn test ga.test.tsx # 実行結果 PASS __test__/tech/ga.test.tsx (8.502 s) 人気記事の取得 ✓ Google Analytics の API 疎通を確認 (637 ms) Test Suites: 1 passed, 1 total Tests: 1 passed, 1 total Snapshots: 0 total Time: 8.864 s, estimated 9 s Ran all test suites matching /ga.test.tsx/i. ✨ Done in 12.17s.
.env
で管理した状態で API の疎通テストprivate_key
や client_key
、Google Analytics のビュー ID を環境変数で管理して、API 疎通のテストを行います。// .env GA_CLIENT_EMAIL='~~~~~~~' GA_PRIVATE_KEY='~~~~~~~' GA_VIEW_ID='~~~~~~~'
// src/lib/ga.tsx import { GaxiosResponse } from 'gaxios'; import { google, analyticsreporting_v4 } from 'googleapis'; // JWT 認証 // client_email と private_key のみ必要 // .env で管理 const client = new google.auth.JWT({ email: process.env.GA_CLIENT_EMAIL, key: process.env.GA_PRIVATE_KEY, scopes: ['https://www.googleapis.com/auth/analytics.readonly'], }); const analyticsreporting = google.analyticsreporting({ version: 'v4', auth: client, }); export async function fetchPostGaData(): Promise< GaxiosResponse<analyticsreporting_v4.Schema$GetReportsResponse> > { const params = { requestBody: { reportRequests: [ { // Google Analytics のビュー ID // .env で管理 viewId: process.env.GA_VIEW_ID, dimensions: [ { name: 'ga:pagePath', }, ], // 取得したい metrics // 今回は PV 数のみ取得 metrics: [ { expression: 'ga:pageviews', }, ], // 並び順 // 今回は PV を基準に、降順で取得 orderBys: [ { fieldName: 'ga:pageviews', sortOrder: 'DESCENDING', }, ], // 期間 dateRanges: [ { startDate: '1daysAgo', endDate: '1daysAgo', }, ], // ページの最大取得数 pageSize: 3, // パスのフィルタリング dimensionFilterClauses: [ { filters: [ { dimensionName: 'ga:pagePath', operator: 'REGEXP', expressions: ['/test'], }, ], }, ], }, ], }, }; return await analyticsreporting.reports.batchGet(params); }
# npm の場合 npm test ga.test.tsx # yarn の場合 yarn test ga.test.tsx # 実行結果 FAIL __test__/tech/ga.test.tsx (9.02 s) 人気記事の取得 ✕ Google Analytics の API 疎通を確認 (6 ms) ● 人気記事の取得 › Google Analytics の API 疎通を確認 No key or keyFile set. 65 | }, 66 | }; > 67 | return await analyticsreporting.reports.batchGet(params); | ^ 68 | } 69 | 70 | // export async function fetchPopularPosts(): Promise<
テストを始める前に@next/env
のloadEnvConfig()
で環境変数ファイルを読み込む作戦です。
これで.env や.env.test.local などで定義した環境変数をテスト環境でも利用できます。
loadEnvConfig()
はprocess.env.PWD
だけでも動きますが、Windows
環境ではprocess.env.PWD
がundefined
を返す可能性があるため、process.cwd()
も追加しています。
引用:環境変数を含む Next.js のコードを Jest でテストする - Breath Note
setupEnv.ts
ファイルを作成し、jest.config.js
を設定します。コードは以下の通りです。// src/lib/test/setupEnv.ts import { loadEnvConfig } from '@next/env'; export default async (): Promise<void> => { loadEnvConfig(process.env.PWD || ''); };
// jest.config.js module.exports = { ... globalSetup: '<rootDir>/src/lib/test/setupEnv.ts', ... }
上記の方法では.env
を読み込むことができますが、.env.local
等には対応していないため、注意しましょう。
process.env.GA_PRIVATE_KEY
です。process.env.GA_PRIVATE_KEY
の改行コードを元に戻さなくてはいけません。process.env.GA_PRIVATE_KEY
の改行コードをリプレイスしましょう。// src/lib/ga.tsx ... // JWT 認証 // client_email と private_key のみ必要 // .env で管理 const client = new google.auth.JWT({ email: process.env.GA_CLIENT_EMAIL, // 改行コードをリプレイス key: process.env.GA_PRIVATE_KEY?.replace(/\\n/g, '\n'), scopes: ['https://www.googleapis.com/auth/analytics.readonly'], }); ...
# npm の場合 npm test ga.test.tsx # yarn の場合 yarn test ga.test.tsx # 実行結果 PASS __test__/tech/ga.test.tsx (9.385 s) 人気記事の取得 ✓ Google Analytics の API 疎通を確認 (1241 ms) Test Suites: 1 passed, 1 total Tests: 1 passed, 1 total Snapshots: 0 total Time: 9.843 s, estimated 10 s Ran all test suites matching /ga.test.tsx/i. ✨ Done in 12.16s.
private_key
と client_key
を環境変数で管理できたため、JSON 形式の鍵は削除して構いません!startDate
filtersExpressions
pageSize
1000
を設定// __test__/tech/ga.test.tsx describe('人気記事の取得', () => { test('Google Analytics の API 疎通を確認', async () => { const { status } = await fetchPostGaData({ startDate: '7daysAgo', filtersExpressions: ['/test'], pageSize: 3, }); expect(status).toBe(200); }); });
# npm の場合 npm test ga.test.tsx # yarn の場合 yarn test ga.test.tsx # 実行結果 FAIL __test__/tech/ga.test.tsx ● Test suite failed to run __test__/tech/ga.test.tsx:5:46 - error TS2554: Expected 0 arguments, but got 1. 5 const { status } = await fetchPostGaData({ ~ 6 startDate: '7daysAgo', ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ... 8 pageSize: 3, ~~~~~~~~~~~~~~~~~~ 9 }); ~~~~~
fetchPostGaData
関数が受け取れるようにコードを変更しましょう。// src/lib/ga.tsx ... // 引数の型定義 interface Args { startDate: '1daysAgo' | '7daysAgo'; filtersExpressions: Array<string>; pageSize: number; } // 引数をセット export async function fetchPostGaData({ startDate, filtersExpressions, pageSize, }: Args): Promise<GaxiosResponse<analyticsreporting_v4.Schema$GetReportsResponse>> { const params = { requestBody: { reportRequests: [ { viewId: process.env.GA_VIEW_ID, dimensions: [ { name: 'ga:pagePath', }, ], metrics: [ { expression: 'ga:pageviews', }, ], orderBys: [ { fieldName: 'ga:pageviews', sortOrder: 'DESCENDING', }, ], dateRanges: [ { startDate: startDate, endDate: '1daysAgo', }, ], pageSize: pageSize, dimensionFilterClauses: [ { filters: [ { dimensionName: 'ga:pagePath', operator: 'REGEXP', expressions: filtersExpressions, }, ], }, ], }, ], }, }; return await analyticsreporting.reports.batchGet(params); } ...
# npm の場合 npm test ga.test.tsx # yarn の場合 yarn test ga.test.tsx # 実行結果 PASS __test__/tech/ga.test.tsx 人気記事の取得 ✓ Google Analytics の API 疎通を確認 (1186 ms) Test Suites: 1 passed, 1 total Tests: 1 passed, 1 total Snapshots: 0 total Time: 3.198 s, estimated 9 s Ran all test suites matching /ga.test.tsx/i. ✨ Done in 5.47s.
/blog/*
/tech/*
/news/*
// __test__/tech/ga.test.tsx import { fetchPostGaData, getPopularPaths } from 'lib/ga'; describe('人気記事の取得', () => { ... // 追記 test('ブログ記事、技術記事、ニュース記事の中から人気記事を抽出', async () => { const popularPaths = await getPopularPaths(); // ブログ記事が少なくとも 1 記事含む expect(popularPaths.some((path) => path.startsWith('/blog/'))).toBeTruthy(); // 技術記事が少なくとも 1 記事含む expect(popularPaths.some((path) => path.startsWith('/tech/'))).toBeTruthy(); // ニュース記事が少なくとも 1 記事含む expect(popularPaths.some((path) => path.startsWith('/news/'))).toBeTruthy(); // ブログ記事、技術記事、ニュース記事以外が存在しない expect( popularPaths.some( (path) => path.startsWith('/blog/') || path.startsWith('/tech/') || path.startsWith('/news/'), ), ).toBeTruthy(); }); });
// src/lib/ga.tsx ... export async function getPopularPaths(): Promise<string[]> { const popularPosts: string[] = []; return popularPosts; } ...
# npm の場合 npm test ga.test.tsx # yarn の場合 yarn test ga.test.tsx # 実行結果 FAIL __test__/tech/ga.test.tsx (9.545 s) 人気記事の取得 ✓ Google Analytics の API 疎通を確認 (683 ms) ✕ ブログ記事、技術記事、ニュース記事の中から人気記事を抽出 (2 ms) ● 人気記事の取得 › ブログ記事、技術記事、ニュース記事の中から人気記事を抽出 expect(received).toBeTruthy() Received: false 16 | 17 | // ブログ記事が少なくとも 1 記事含む > 18 | expect(popularPaths.some((path) => path.startsWith('/blog/'))).toBeTruthy();
getPopularPaths
関数を実装します。まず、ブログ記事のみ抽出してみましょう。// src/lib/ga.tsx ... export async function fetchPopularPosts( pageSize: number, ): Promise<GaxiosResponse<analyticsreporting_v4.Schema$GetReportsResponse>> { const res = await fetchPostGaData({ startDate: '7daysAgo',// 期間は過去 1 週間 filtersExpressions: ['^/(blog)/[0-9a-zA-Z\\-]+$'],// /blog/~~~のみ、正規表現で抽出 pageSize: pageSize,// 取得最大数 }); return res; } export async function getPopularPaths(pageSize = 1000): Promise<string[]> { const { reports } = (await fetchPopularPosts(pageSize)).data; // GA の行データを取得 const gaRowData = reports ? reports[0]?.data?.rows ?? [] : []; // パス取得 const popularPaths = gaRowData .filter((row) => row.dimensions && row.dimensions[0]) // dimensions や dimensions[0] が存在するデータのみ抽出 .map((row) => { return row.dimensions![0]; }); return popularPaths; } ...
# npm の場合 npm test ga.test.tsx # yarn の場合 yarn test ga.test.tsx # 実行結果 FAIL __test__/tech/ga.test.tsx (9.81 s) 人気記事の取得 ✓ Google Analytics の API 疎通を確認 (579 ms) ✕ ブログ記事、技術記事、ニュース記事の中から人気記事を抽出 (631 ms) ● 人気記事の取得 › ブログ記事、技術記事、ニュース記事の中から人気記事を抽出 expect(received).toBeTruthy() Received: false 18 | expect(popularPaths.some((path) => path.startsWith('/blog/'))).toBeTruthy(); 19 | // 技術記事が少なくとも 1 記事含む > 20 | expect(popularPaths.some((path) => path.startsWith('/tech/'))).toBeTruthy();
// src/lib/ga.tsx ... export async function fetchPopularPosts( pageSize: number, ): Promise<GaxiosResponse<analyticsreporting_v4.Schema$GetReportsResponse>> { const res = await fetchPostGaData({ startDate: '7daysAgo',// 期間は過去 1 週間 // |tech|news を追記 filtersExpressions: ['^/(blog|tech|news)/[0-9a-zA-Z\\-]+$'], pageSize: pageSize,// 取得最大数 }); return res; } ...
# npm の場合 npm test ga.test.tsx # yarn の場合 yarn test ga.test.tsx # 実行結果 PASS __test__/tech/ga.test.tsx (9.585 s) 人気記事の取得 ✓ Google Analytics の API 疎通を確認 (600 ms) ✓ ブログ記事、技術記事、ニュース記事の中から人気記事を抽出 (723 ms) Test Suites: 1 passed, 1 total Tests: 2 passed, 2 total Snapshots: 0 total Time: 10.019 s Ran all test suites matching /ga.test.tsx/i. ✨ Done in 12.46s.
新着記事
関連記事
著者