Más contenido relacionado

Testing at Both Ends of the Triangle

  1. Testingat Both Endsofthe Triangle
  2. heyhihello derek graham @deejaygraham @deejaygraham@hachyderm.io
  3. https://nebytes.net
  4. The. Testing. Triangle.
  5. Testcodeiscode
  6. Testcodeiscode th@t wE d0n;t tesT’
  7. Microtests
  8. A test is not a unit test if: * It talks to the database * It communicates across the network * It touches the file system * It can't run at the same time as any of your other unit tests * You have to do special things to your environment (such as editing config files) to run it. Michael Feathers - A Set of Unit Testing Rules
  9. EndtoEndTests
  10. IntegratedTests
  11. Web2.0
  12. S cr ipt
  13. CypressGood:)
  14. • Runs “inside” browser • Automatic waiting • Debuggability • Snapshots • Spies
  15. • Network interception • Screenshots & videos • Hot reload • Auto retry
  16. CypressBad:(
  17. • No multiple tabs • Not established like Se • JS only • No Safari • Iframes limited
  18. • Multi-site not allowed • Login still an issue
  19. npminstallcypress
  20. https://docs.cypress.io
  21. npxcypressopen
  22. • application • src • cypress • components • e2e • support
  23. Let’stalk about Jest(maybe)
  24. describe('The thing I am testing', () => { it('should do this', () => { expect(1 + 1).toBe(2); }); it('ought not to do that', () => { expect('red').toBe('green'); }); });
  25. cy.*
  26. E2Etests
  27. Arrange Act Assert
  28. Arrange Act Assert
  29. cy.visit('https://google.com');
  30. before(() => { cy.clearLocalStorage(); cy.visit(examplePage); });
  31. Arrange Act Assert
  32. cy.get(‘.username'); cy.get(‘#password'); cy.get('input[type=submit]');
  33. cy.get('#password') .type('random password');
  34. • Use data-cy attribute • Visible text • Element ids • Class name • Generic control name https://docs.cypress.io/guides/references/best-practices
  35. Arrange Act Assert
  36. cy.contains('Google'); cy.contains('google', { matchCase: false }); cy.title().should(‘contain', ‘TotT'); cy.get('#password').should('not.contain', 'swordfish'); cy.get('#password') .should('not.contain', 'passw0rd') .and('not.contain', 'swordfish'); cy.get('#password') .type('random password') .should('have.value', 'random password');
  37. Should
  38. cy.get('h1') .invoke('attr', 'class') .should('eq', 'title');
  39. • be.visible • have.attr • have.value • have.text • not.exist
  40. So,areyou feeling lucky?
  41. describe('Google', () => { it('Knows about Tech on the Tyne', () => { cy.viewport(1280, 720); cy.intercept(‘https://www.google.com/search*') .as('google'); cy.visit('https://www.google.com/'); // find reject all button... cy.get('#W0wltc').click(); cy.get('input[name="q"]') .type('Tech on the Tyne'); cy.get('form').submit(); cy.wait('@google'); }); });
  42. E2EDemo
  43. MoreExamples
  44. cy.window().then((win) => { win.navigator .clipboard .readText().then((text) => { expect(text) .to .contain(‘Hello this is in the clipboard'); }); });
  45. const block = cy.get('#content') .find('.notification'); block.should('be.visible'); block.within(() => { cy.get('p').contains('Hello World'); });
  46. cy.get('.article').within(($article) => { if ($article.find('img').length > 0) { cy.get('img').each(($img) => { cy.wrap($img).scrollIntoView() .should('be.visible'); expect($img[0].naturalWidth) .to.be.greaterThan(0); expect($img[0].naturalHeight) .to.be.greaterThan(0); }); } });
  47. it('Web API works with Cypress', () => { cy.request({ url: 'https://swapi.dev/api/people/1', method: 'GET' }).its('body') .should('deep.contain', { name: 'Anakin Skywalker' }) })
  48. it('Website talks to fake Star Wars API', () => { cy.intercept( 'GET', 'https://swapi.dev/api/people/1', { statusCode: 200, body: { name: 'Peter Pan', }, }) })
  49. Componenttests
  50. https://github.com/ deejaygraham/calendar
  51. ComponentDemo
  52. npxcypressrun
  53. • Component Level Tests • Sandboxed • Time travel • Before and After • Same dev experience as e2e
  54. Debugging
  55. cy.log('hello world’); cy.debug();
  56. Its&invoke
  57. // access session storage cy.window().its('sessionStorage'); // get access to the redux store cy.window().its('store'); // dispatch an object to redux cy.window() .its('store') .invoke('dispatch', { type: 'description', value: 'smol' });
  58. CustomCommands
  59. Cypress.Commands.add('store', () => { return cy.log('Redux - Store') .window({ log: false }) .its('store', { log: false }); }); Cypress.Commands.add('dispatch', (obj) => { const { type, ...data } = obj; return cy.log(`Redux - Dispatch: ${type}`, data) .window({ log: false }) .its('store', { log: false }) .invoke('dispatch', obj); });
  60. cy.store() .dispatch( { type: 'description', value: 'smol' });
  61. Spies&Stubs
  62. const onChangeSpy = cy.spy().as('onChangeSpy'); cy.mount(<Counter onChange={onChangeSpy} />); cy.get('[data-cy=increment]').click(); cy.get('@onChangeSpy') .should('have.been.calledWith', 1);
  63. const onClick = cy.stub(); cy.mount(<Counter onClick={onClick} />); cy.get('[data-cy=increment]') .click() .then(() => { expect(onClick).to.be.called // expect(onClick).to.not.be.called });
  64. @deejaygraham