cypress- Network Requests

코드위의승부사·2020년 7월 28일
0

Testing Strategies

Common testing scenarios :

  • Asserting on a request's body, Asserting on a request's url, Asserting on a request's headers
  • Stubbing a response's body, Stubbing a response's status code, Stubbing a response's headers
  • Delaying a response, Waiting for a response to happen

응답을 Cypress 내에서 stub 할건지 혹은 서버로 전송할건지 선택할 수 있다.(mix match도 가능)

  1. stub Requests(Fast, Easy, Flexible, No server/DB, Not True E2E, Require Fixture)
    requests for data are intercepted by Cypress
    Control the network
  • cy.server()
  • cy.route()
  • set status codes
  • set response bodies
  • Test edge cases
  1. Static User(seed your sever starategy)
    : Real Session E2E, Require Server, seed the Db, shares Test State

  2. Dynamic user(modify Db within Tests, Query Db within Tests)
    : No State Mutations, Flexible/Powerful, Db Setup/TearDown, Slow / Complex

Use Server Responses

Request가 stub 되지 않고 서버에 도달하기 때문에 서버와 클라이언트의 작동을 보장한다. E2E테스트

When requests are not stubbed, this guarantees that the contract between your client and server is working correctly.

stub 되지 않는 response들에 대한 단점

  • 실제 응답(real responses)을 보내기 때문에 테스트하기전에 매번 db를 seed해줘야한다.
    (예: pagination을 테스트 하기 위해서는 필요한 db의 모든 객체를 seed 해줘야할것이다.)
  • 실제 응답은 서버의 모든 레이어(controller, models, views)를 거치기 때문에 stub responses보다 더 느리다.

JSON을 전송해주는 요즘 앱에서는 stubbing의 장점을 사용할 수 있다.

장점

  • 실제 프로덕트 같은 환경
  • 서버의 엔드포인트까지의 커버리지
  • 전통적인 서버사이드 HTML 렌더링에 강점

단점

  • seeding data의 필요성
  • 보다 느림(than stubbed responses)
  • edge cases의 테스트 어려움

추천 사례

  • 사용 빈도를 줄이고, 앱에서 critical한 경로(로그인, 회원가입, 결제), 오류가 적고 예외적인 case에 대해 한가지테스트를 만드는것

Stub Responses

stub response는 response body, status, headers, even network delay까지 response의 모든 면을 컨트롤 할 수 있게 해준다.

Stubbing responses is a great way to control the data that is returned to your client.

앱의 requests 들은 stub되기 때문에 어떤 코드 수정도 필요없다.

장점

  • response bodies, status, headers의 컨트롤
  • 네트워크 지연되는 응답을 강제할수 있음
  • (서버나 클라이언트의)코드의 변화가 필요없다
  • 20ms보다 빠른 속도

단점

  • stub responses가 실제 데이터와 매치를 보장하지 않음
  • 서버의 특정 엔드포인트에 테스트 제한
  • 전통적인 서버사이드 HTML 렌더링에는 유용성 없음

추천 사례

  • 대부분 테스트에 사용 가능, Mix and Match(하나의 E2E 테스트 + 나머지 stub), JSON API에 최적

Stubbing

cypress는 response를 stub하고 body, status, headers, delay까지 컨트롤 한다.

response를 stubbing하기 위해서는 두가지가 필요하다
1. Start a cy.server()
2. Provide a cy.route()
이 두가지 커맨드가 같이 작동하며 response를 커맨드들의 옵션으로 조작한다.
cy.server() 는 stubbing을 가능하게 하고, cy.route()가 routing table을 제공하면 Cypress는 어떤 응답이 어떤 리퀘스트로 가야하는지 이해한다.

Requests

Cypress는 XHR 요청이 일어나면 Command Log에 자동적으로 나타내준다.(requests가 시작되거나 끝날 경우에)
리퀘스트 전후의 순간 DOM 스냅샷을 찍을 수 있다.
Cypress는 Command Log의 간결성을 위해 .js 나 .html같은 정적 콘텐츠의 fetch를 무시하도록 되도록 설정되있지만 cy.server() 옵션에 의해 바꿔줄 수 있다.
Cypress는 자동적으로 request header 그리고 body를 모아 사용할 수 있게 만들어준다.

Routing

cy.server()           // enable response stubbing
cy.route({
  method: 'GET',      // Route all GET requests
  url: '/users/*',    // that have a URL that matches '/users/*'
  response: []        // and force the response to be: []
})

cy.server()를 시작하고 cy.route() 커맨드를 정의 한다면, Cypress는 Routes를 커맨드 로그에 보여줄 것이다.
cy.server()를 시작하면 모든 요청들은 나머지 테스트들에 의해 컨트롤 될 수 있다.
새로운 테스트를 실행하면, Cypress는 기본적인 행태를 다시 저장하고 모든 라우팅과 stubbing을 삭제할 것이다.

Fixture

Fixture는 테스트에 존재하는 고정적인 데이터들이 위치한 파일이다.
Fixture test 목적은 테스트들이 고정적인 환경에서 반복할수있게 하는것이다.
Cypress에서는 네트웍 리퀘스트를 stub 할 수 있고, fixture data에 의해 응답을 즉시 가질수 있다.
응답을 stubbing 할 때, 크기가 큰 JSON객체를 관리해야 할 경우, Cypress는 fixture 문법을 응답에 직접 통합할 수 있게 해준다.

cy.server()

// we set the response to be the activites.json fixture
cy.route('GET', 'activities/*', 'fixture:activities.json')

응답을 aliases로 추가적으로 참고할 수 있다.
Aliases는 fixture를 직접 가르킬 필요는 없지만, 일반적인 사용 사례다.
fixture를 분리하게 되면 응답에 먼저 객체를 변형시키고 조작할 수 있다.

cy.server()

cy.fixture('activities.json').as('activitiesJSON')
cy.route('GET', 'activities/*', '@activitiesJSON')

Organizing

새 프로젝트의 fixture을 추천 폴더 구조에 자동적으로 넣어논다.
기본적으로 example.json에 생성된다.

/cypress/fixtures/example.json

Fixture은 추가 폴더에 저장될 수 도있다.
For instance, you could create another folder called images and add images:

/cypress/fixtures/images/cats.png
/cypress/fixtures/images/dogs.png
/cypress/fixtures/images/birds.png

fixture에 접근하는 방법

cy.fixture('images/dogs.png) // returns dogs.png as Base64

Waiting

stub 응답을 선택하거나 말거나 Cypress는 당신의 요청과 응답을 선언적으로 기다릴 수 있게 해준다.

이 부분에서는 Aliasing 이라는 개념을 사용한다.

cy.server()
cy.route('activities/*', 'fixture:activities').as('getActivities')
cy.route('messages/*', 'fixture:messages').as('getMessages')


// visit the dashboard, which should make requests that match
// the two routes above
cy.visit('http://localhost:8888/dashboard')

// pass an array of Route Aliases that forces Cypress to wait
// until it sees a response for each request that matches
// each of these aliases
cy.wait(['@getActivities', '@getMessages'])

// these commands will not run until the wait command resolves above
cy.get('h1').should('contain', 'Dashboard')

각 aliased 된 라우트들의 응답을 확인하기 위해 cy.wait() 호출을 사용하십시오.

cy.server()
cy.route({
  method: 'POST',
  url: '/myApi',
}).as('apiCheck')

cy.visit('/')
cy.wait('@apiCheck').then((xhr) => {
  assert.isNotNull(xhr.response.body.data, '1st API call has data')
})

cy.wait('@apiCheck').then((xhr) => {
  assert.isNotNull(xhr.response.body.data, '2nd API call has data')
})

cy.wait('@apiCheck').then((xhr) => {
  assert.isNotNull(xhr.response.body.data, '3rd API call has data')
})

aliased route를 갖는 건 큰 장점들을 갖는다.
1. 견고한 테스트
2. 실패 메시지를 보다 간결해짐
3. XHR object에 근거해 assert 할 수 있다.

Flake

응답에 대해 기다림을 선언하는건 테스트 flake를 감소시킨다. 응답이 특정 라우트의 alias에 매치되는걸 예상할 때 Cypress에게 지시하는 가드로 cy.wait()을 생각할 수 있다.
이렇게 하면 응답이 다시 올 때까지 다음 명령이 실행되지 않고 요청이 처음 지연되는 상황을 방지할 수 있다.
Cypress는 자동적으로 getSearch alias에 매치되는 리퀘스트에 대해 기다리는게 강점이다.
Cypress의 성공적인 요청에 대한 부작용을 테스트 하는 대신 결과에 대한 실제 원인을 테스트 할 수 있다.

cy.server()
cy.route('/search*', [{ item: 'Book 1' }, { item: 'Book 2' }]).as('getSearch')

// our autocomplete field is throttled
// meaning it only makes a request after
// 500ms from the last keyPress
cy.get('#autocomplete').type('Book')

// wait for the request + response
// thus insulating us from the
// throttled request
cy.wait('@getSearch')

cy.get('#results')
  .should('contain', 'Book 1')
  .and('contain', 'Book 2')

Failures

검색 결과가 앱의 몇 가지 항목과 연결된다.
1. 앱이 정확한 URL에 request를 만들고 있다.
2. 앱의 응답이 정확히 진행되는지
3. 앱의 DOM에 결과를 삽입하는것
위의 사례에서 다양한 실패의 원인들이 있다.
다른 테스팅 툴에 비해 cy.wait()을 사용해서 에러를 더욱 명확히 할 수 있다.

Assertions

cy.wait()의 다른 장점은 실제 XHR object에 대한 접근을 허용해 주는 것이다.
이 객체에 assertion을 만드는 경우 유용하다.
아래의 예에서는 요청 객체가 URL을 쿼리 스트링의 데이터를 보냈는지 확인할 수 있다.
cy.wait()를 산출하는 XHR 객체는 모든 것에 대한 assertion이 가능하다.
(URL, Method, Status Code, Request Body, Request Headers, Response Body, Response Headers)

cy.server()
// any request to "search/*" endpoint will automatically receive
// an array with two book objects
cy.route('search/*', [{ item: 'Book 1' }, { item: 'Book 2' }]).as('getSearch')

cy.get('#autocomplete').type('Book')

// this yields us the XHR object which includes
// fields for request, response, url, method, etc
cy.wait('@getSearch')
  .its('url').should('include', '/search?query=Book')

cy.get('#results')
  .should('contain', 'Book 1')
  .and('contain', 'Book 2')
cy.server()
// spy on POST requests to /users endpoint
cy.route('POST', '/users').as('new-user')
// trigger network calls by manipulating web app's user interface, then
cy.wait('@new-user')
  .should('have.property', 'status', 201)

// cy.wait('@new-user').then(console.log)
// we can grab the completed XHR object again to run more assertions
// using cy.get(<alias>)
cy.get('@new-user') // yields the same XHR object
  .its('requestBody') // alternative: its('request.body')
  .should('deep.equal', {
    id: '101',
    firstName: 'Joe',
    lastName: 'Black'
  })

// and we can place multiple assertions in a single "should" callback
cy.get('@new-user')
  .should((xhr) => {
    expect(xhr.url).to.match(/\/users$/)
    expect(xhr.method).to.equal('POST')
    // it is a good practice to add assertion messages
    // as the 2nd argument to expect()
    expect(xhr.response.headers, 'response headers').to.include({
      'cache-control': 'no-cache',
      expires: '-1',
      'content-type': 'application/json; charset=utf-8',
      location: '<domain>/users/101'
    })
  })
profile
함께 성장하는 개발자가 되고 싶습니다.

0개의 댓글