Cypress系列(101)- intercept() 命令詳解

小菠蘿測試筆記發表於2020-12-07

如果想從頭學起Cypress,可以看下面的系列文章哦

https://www.cnblogs.com/poloyy/category/1768839.html

 

作用

使用該命令在網路層管理 HTTP 請求的行為

 

包含以下功能

  • 對任何型別的 HTTP 請求進行 stub 或 spy
  • 在 HTTP 請求傳送到目標伺服器前,可以修改 HTTP 請求 body、headers、URL(類似抓包工具對請求進行打斷點然後修改)
  • 動態或靜態地對 HTTP 請求的響應進行 stub
  • 接收 HTTP 響應後可對 HTTP 響應 body、headers、status、code 進行修改(類似抓包工具對響應進行打斷點然後修改)
  • 在所有階段都可以完全訪問所有 HTTP 請求

 

相較於 cy.route() 的不同

 cy.route() 命令詳解:https://www.cnblogs.com/poloyy/p/13852941.html

  • 可以攔截所有型別的網路請求,包括 Fetch API,頁面載入,XMLHttpRequest,資源載入等
  • 不需要在使用前呼叫 cy.server() ,實際上 cy.server() 根本不影響 cy.intercept() 
  • 預設情況下沒有將請求方法設定為 GET

 

語法格式

cy.intercept(url, routeHandler?)
cy.intercept(method, url, routeHandler?)
cy.intercept(routeMatcher, routeHandler?)

 

url

要匹配的請求 URL ,可以是字串也可以是正規表示式

cy.intercept('http://example.com/widgets')
cy.intercept('http://example.com/widgets', { fixture: 'widgets.json' })

沒有指定請求方法的話,可以匹配任意型別的請求方法

 

method

請求方法

cy.intercept('POST', 'http://example.com/widgets', {
  statusCode: 200,
  body: 'it worked!'
})

 

routeMatcher 

  • 它是一個物件
  • 用於匹配此路由將處理哪些傳入的 HTTP 請求
  • 所有物件屬性都是可選的,不是必填的
  • 設定的所有屬性必須與路由匹配才能處理請求
  • 如果將字串傳遞給任何屬性,則將使用 minimatch 將與請求進行全域性匹配

它有以下屬性

{
  /**
   * 與 HTTP Basic身份驗證中使用的使用者名稱和密碼匹配
   */
  auth?: { username: string | RegExp, password: string | RegExp }

  /**
   * 與請求上的 HTTP Headers 匹配
   */
  headers?: {
    [name: string]: string | RegExp
  }

  /**
   * 與請求上的 hostname 匹配
   */
  hostname?: string | RegExp

  /**
   * If 'true', 只有 https 的請求會被匹配
   * If 'false', 只有 http 的請求會被匹配
   */
  https?: boolean

  /**
   * 與請求上的 method 請求方法匹配
   * 預設 '*', 匹配全部型別的 method
   */
  method?: string | RegExp

  /**
   * 主機名後的路徑, 包括了 ? 後面的查詢引數
   * www.baidu.com/s?wd=2
   */
  path?: string | RegExp

  /**
   * 和 path 一樣, 不過不管 ? 後面的查詢引數
   * www.baidu.com/s
   */
  pathname?: string | RegExp

  /**
   * 與指定的埠匹配, 或者傳遞多個埠組成的陣列, 其中一個匹配上就行了
   */
  port?: number | number[]

  /**
   * 與請求路徑 ? 後面跟的查詢引數匹配上
   * wd=2
   */
  query?: {
    [key: string]: string | RegExp
  }

  /**
   * 完整的請求 url
   * http://www.baidu.com/s?wd=2
   */
  url?: string | RegExp
}

   

routeHandler 

  • routeHandler 定義瞭如果請求和 routeMatcher 匹配將對請求進行的指定的處理
  • 可接受的資料型別:string、object、Function、StaticResponse

 

StaticResponse

  • 相當於一個自定義響應體物件
  • 可以自定義 Response headers、HTTP 狀態碼、Response body 等
  • 詳細栗子將在後面展開講解

 

StaticResponse 物件的屬性

{
  /**
   * 將 fixture 檔案作為響應主體, 以 cypress/fixtures 為根目錄
   */
  fixture?: string
  /**
   * 將字串或 JSON 物件作為響應主體
   */
  body?: string | object | object[]
  /**
   * 響應 headers
   * @default {}
   */
  headers?: { [key: string]: string }
  /**
   * 響應狀態碼
   * @default 200
   */
  statusCode?: number
  /**
   * 如果 true, Cypress 將破壞網路連線, 並且不傳送任何響應
   * 主要用於模擬無法訪問的伺服器
   * 請勿與其他選項結合使用
   */
  forceNetworkError?: boolean
  /**
   * 傳送響應前要延遲的毫秒數
   */
  delayMs?: number
  /**
   * 以多少 kbps 傳送響應體
   */
  throttleKbps?: number
}

 

string

  • 如果傳遞一個字串,這個值相當於響應 body 的值
  • 等價於 StaticResponse 物件 { body: "foo" } 

 

object

  • 如果傳遞了沒有 StaticResponse 金鑰的物件,則它將作為 JSON 響應 Body 傳送
  • 例如, {foo:'bar'} 等價於 StaticResponse 物件 {body:{foo:'bar'}} 

 

function

  • 如果傳遞了一個回撥函式,當一個請求匹配上了該路由將會自動呼叫這個函式
  • 函式第一個引數是請求物件
  • 在回撥函式內部,可以修改外發請求、傳送響應、訪問實際響應
  • 詳細栗子將在後面展開講解

 

命令返回結果

  • 返回 null
  • 可以連結 as() 進行別名,但不可連結其他命令
  • 可以使用 cy.wait() 等待 cy.intercept() 路由匹配上請求,這將會產生一個物件,包含匹配上的請求/響應相關資訊

 

實際栗子的前置準備

Cypress 官方專案的下載地址:https://github.com/cypress-io/cypress-example-kitchensink

 

下載好後進入下圖專案資料夾

 

啟動專案

npm start

 

通過 URL 路由匹配請求的栗子

測試程式碼

 

等價於 route() 的測試程式碼

注:  route()  未來將會被棄用

 

執行結果

登入請求匹配上了路由

 

Console 檢視 cy.wait() 返回的物件

最重要的當然是 request 和 response 兩個屬性

 

通過 RouteMatcher 路由匹配請求的栗子

測試程式碼

斷言請求體和響應狀態碼

 

執行結果

 

Console 檢視 cy.wait() 返回的物件

 

另一種斷言方式

// 斷言匹配此路由的請求接收到包含【username】的請求 body
cy.wait('@login3').its('request.body').should('have.property', 'username')

// 斷言匹配此路由的請求接收到 HTTP 狀態碼為 500
cy.wait('@login3').its('response.statusCode').should('eq', 200)

// 斷言匹配此路由的請求接收到包含【redirect】的請求 body
cy.wait('@login3').its('response.body').should('have.property', 'redirect')

不過這樣的話只能每次寫一條不能同時三條都寫,所以還是建議像程式碼圖一樣,先 .then() 再進行斷言

 

自定義不同型別的響應體的各種栗子

自定義一個純字串的響應體

測試程式碼

 

執行結果

 

介面響應

 

自定義一個 JSON 的響應體

測試程式碼

會從cypress安裝目錄/fixtures 下讀取對應的資料檔案,它會變成響應 body 的資料

 

test.json 資料檔案

 

執行結果

 

介面響應

 

自定義一個 StaticResponse 的響應體

測試程式碼

自定義了響應body、statusCode,還有返回響應的延時時間

 

執行結果

延時生效了

 

body 和 statusCode 變成自定義的資料了

 

攔截請求的栗子

前置操作

beforeEach(() => {
    cy.visit('http://localhost:7079/login')
})

 

斷言請求的栗子

測試程式碼

 

執行結果

 

Console 檢視列印結果

可以看到回撥函式只有一個引數,就是 request 引數

 

重點

回撥函式內不能包含 cy.**() 的命令,如果包含會報錯

簡單來說就是

 cy.type() 命令執行完後會返回一個 promise 物件,同時又會呼叫回撥函式,而回撥函式內又呼叫了 cy.get() 返回了一個 promise 物件,Cypress 會將這種情況當做測試失敗處理

 

將請求傳遞給下一個路由處理程式

前言

意思就是一個請求可以同時匹配上多個路由

 

測試程式碼

 

執行結果

一個登入請求匹配成功了兩個路由,且回撥函式會按匹配的順序執行

 

總結

回撥函式的引數就是一個請求物件,它其實可以呼叫以下方法

{
  /**
   * 銷燬該請求並返回網路錯誤的響應
   */
  destroy(): void

  /**
   * 控制請求的響應
   * 如果傳入的是一個函式, 則它是回撥函式, 當響應時會呼叫
   * 如果傳入的是一個 StaticResponse 物件, 將不會發出請求, 而是直接將這個物件當做響應返回
   */
  reply(interceptor?: StaticResponse | HttpResponseInterceptor): void

  /**
   * 使用 response body(必填) 和 response header(可選) 響應請求
   */
  reply(body: string | object, headers?: { [key: string]: string }): void

  /**
   * 使用 HTTP 狀態碼(必填)、 response body(可選)、response header(可選) 響應請求
   */
  reply(status: number, body?: string | object, headers?: { [key: string]: string }): void

  /**
   * 重定向到新的 location 來響應請求,
   * @param statusCode 用來重定向的 HTTP 狀態程式碼, Default: 302
   */
  redirect(location: string, statusCode?: number): void
}

 

攔截響應的栗子

req.reply() 函式詳解

前言

可以使用 req.reply() 函式來動態控制對請求的響應

 

使用講解

cy.intercept('/login', (req) => {
    // functions on 'req' can be used to dynamically respond to a request here

    // 將請求傳送到目標伺服器
    req.reply()

    // 將這個 JSON 物件響應請求
    req.reply({plan: 'starter'})

    // 將請求傳送到目標伺服器, 並且攔截伺服器返回的實際響應, 然後進行後續操作(類似抓包工具對響應打斷點)
    req.reply((res) => {
        // res 就是實際的響應物件
    })
})

 

.reply() 直接修改響應的栗子

測試程式碼

 

介面響應內容

 

攔截響應的小栗子

測試程式碼

 

執行結果

 

Console 檢視列印結果

一個是 request 物件,一個是 response 物件

 

自定義響應內容

前言

  • 可以使用 resp.send() 函式動態控制傳入的響應
  • 另外,當響應傳送到瀏覽器時,對 resp 的任何修改都將保留
  • 如果尚未呼叫 resp.send() ,則它會在 req.reply() 回撥函式完成後隱式呼叫

 

使用講解

cy.intercept('/notification', (req) => {
    req.reply((resp) => {
        // Success 將作為 response body 返回到瀏覽器
        resp.send('Success')

        // 將 success.json 裡面的資料作為 response body 返回到瀏覽器
        resp.send({fixture: 'success.json'})

        // 將響應延遲 1000ms
        resp.delay(1000)

        // 將響應限制為 64kbps
        resp.throttle(64)
    })
})

 

傳遞字串作為響應內容

測試程式碼

 

介面響應內容

 

傳遞 JSON 物件作為響應內容

測試程式碼

 

介面響應內容

 

傳遞 StaticResponse 物件作為響應內容

測試程式碼

 

介面響應內容

 

resp 可呼叫的函式總結

{
/**
* 可以自定義 response statusCode、response body、response header
* 也可以直接傳 StaticResponse 物件
*/
send(status: number, body?: string | number | object, headers?: { [key: string]: string }): void
send(body: string | object, headers?: { [key: string]: string }): void
send(staticResponse: StaticResponse): void
/**
* 繼續返回響應
*/
send(): void
/**
* 等待 delayMs 毫秒,然後再將響應傳送給客戶端
*/
delay: (delayMs: number) => IncomingHttpResponse
/**
* 以多少 kbps 的速度傳送響應
*/
throttle: (throttleKbps: number) => IncomingHttpResponse
}

 

相關文章