diff --git a/app/commands/spies-stubs-clocks.html b/app/commands/spies-stubs-clocks.html index f5e649865..58f1a0993 100644 --- a/app/commands/spies-stubs-clocks.html +++ b/app/commands/spies-stubs-clocks.html @@ -169,6 +169,99 @@

cy.tick()

+ +

+
+

cy.stub() matches depending on arguments

+

See all possible matchers at Sinonjs.org

+
const greeter = {
+  /**
+    * Greets a person
+    * @param {string} name
+  */
+  greet (name) {
+    return `Hello, ${name}!`
+  },
+}
+
+cy.stub(greeter, 'greet')
+  .callThrough() // if you want non-matched calls to call the real method
+  .withArgs(Cypress.sinon.match.string).returns('Hi')
+  .withArgs(Cypress.sinon.match.number).throws(new Error('Invalid name'))
+
+expect(greeter.greet('World')).to.equal('Hi')
+expect(() => greeter.greet(42)).to.throw('Invalid name')
+expect(greeter.greet).to.have.been.calledTwice
+
+// non-matched calls goes the actual method
+expect(greeter.greet()).to.equal('Hello, undefined!')
+
+ +

+
+

matches call arguments using Sinon matchers

+

See all possible matchers at Sinonjs.org

+
const calculator = {
+  /**
+    * returns the sum of two arguments
+    * @param a {number}
+    * @param b {number}
+  */
+  add (a, b) {
+    return a + b
+  },
+}
+
+const spy = cy.spy(calculator, 'add').as('add')
+
+expect(calculator.add(2, 3)).to.equal(5)
+
+// if we want to assert the exact values used during the call
+expect(spy).to.be.calledWith(2, 3)
+
+// let's confirm "add" method was called with two numbers
+expect(spy).to.be.calledWith(Cypress.sinon.match.number, Cypress.sinon.match.number)
+
+// alternatively, provide the value to match
+expect(spy).to.be.calledWith(Cypress.sinon.match(2), Cypress.sinon.match(3))
+
+// match any value
+expect(spy).to.be.calledWith(Cypress.sinon.match.any, 3)
+
+// match any value from a list
+expect(spy).to.be.calledWith(Cypress.sinon.match.in([1, 2, 3]), 3)
+
+// expect the value to pass a custom predicate function
+// the second argument to "sinon.match(predicate, message)" is
+// shown if the predicate does not pass and assertion fails
+const isEven = (x) => x % 2 === 0
+
+expect(spy).to.be.calledWith(Cypress.sinon.match(isEven, 'isEven'), 3)
+
+// you can combine several matchers using "and", "or"
+const isGreaterThan = (limit) => (x) => x > limit
+const isLessThan = (limit) => (x) => x < limit
+
+expect(spy).to.be.calledWith(
+  Cypress.sinon.match.number,
+  Cypress.sinon.match(
+    isGreaterThan(2), '> 2').and(Cypress.sinon.match(isLessThan(4), '< 4'))
+)
+
+expect(spy).to.be.calledWith(
+  Cypress.sinon.match.number,
+  Cypress.sinon.match(isGreaterThan(200), '> 200').or(Cypress.sinon.match(3))
+)
+
+// matchers can be used from BDD assertions
+cy.get('@add').should('have.been.calledWith',
+  Cypress.sinon.match.number, Cypress.sinon.match(3)
+)
+
+// you can alias matchers for shorter test code
+const { match: M } = Cypress.sinon
+
+cy.get('@add').should('have.been.calledWith', M.number, M(3))
diff --git a/appveyor.yml b/appveyor.yml index 0aa2e663b..b926cfe04 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -21,8 +21,10 @@ install: cache: # cache NPM packages and Cypress binary - - '%AppData%\npm' - - '%USERPROFILE%\AppData\Local\Cypress\Cache' + # and invalidate the cache when package.json file changes + # https://www.appveyor.com/docs/build-cache/ + - '%AppData%\npm -> package.json' + - '%USERPROFILE%\AppData\Local\Cypress\Cache -> package.json' # Post-install test scripts. test_script: @@ -31,6 +33,7 @@ test_script: - run-if npm run cy:version - run-if npm run cy:verify - run-if npm run cy:info + - run-if npm run cy:cache:list - run-if npm run test:ci:record:windows - run-if npm run test:ci:record:windows:edge diff --git a/cypress/integration/examples/spies_stubs_clocks.spec.js b/cypress/integration/examples/spies_stubs_clocks.spec.js index 58031b959..6e6744c91 100644 --- a/cypress/integration/examples/spies_stubs_clocks.spec.js +++ b/cypress/integration/examples/spies_stubs_clocks.spec.js @@ -1,4 +1,6 @@ /// +// remove no check once Cypress.sinon is typed +// https://github.com/cypress-io/cypress/issues/6720 context('Spies, Stubs, and Clock', () => { it('cy.spy() - wrap a method in a spy', () => { @@ -94,4 +96,112 @@ context('Spies, Stubs, and Clock', () => { cy.get('#tick-div').click() .should('have.text', '1489449610') }) + + it('cy.stub() matches depending on arguments', () => { + // see all possible matchers at + // https://sinonjs.org/releases/latest/matchers/ + const greeter = { + /** + * Greets a person + * @param {string} name + */ + greet (name) { + return `Hello, ${name}!` + }, + } + + cy.stub(greeter, 'greet') + .callThrough() // if you want non-matched calls to call the real method + .withArgs(Cypress.sinon.match.string).returns('Hi') + .withArgs(Cypress.sinon.match.number).throws(new Error('Invalid name')) + + expect(greeter.greet('World')).to.equal('Hi') + // @ts-ignore + expect(() => greeter.greet(42)).to.throw('Invalid name') + expect(greeter.greet).to.have.been.calledTwice + + // non-matched calls goes the actual method + // @ts-ignore + expect(greeter.greet()).to.equal('Hello, undefined!') + }) + + it('matches call arguments using Sinon matchers', () => { + // see all possible matchers at + // https://sinonjs.org/releases/latest/matchers/ + const calculator = { + /** + * returns the sum of two arguments + * @param a {number} + * @param b {number} + */ + add (a, b) { + return a + b + }, + } + + const spy = cy.spy(calculator, 'add').as('add') + + expect(calculator.add(2, 3)).to.equal(5) + + // if we want to assert the exact values used during the call + expect(spy).to.be.calledWith(2, 3) + + // let's confirm "add" method was called with two numbers + expect(spy).to.be.calledWith(Cypress.sinon.match.number, Cypress.sinon.match.number) + + // alternatively, provide the value to match + expect(spy).to.be.calledWith(Cypress.sinon.match(2), Cypress.sinon.match(3)) + + // match any value + expect(spy).to.be.calledWith(Cypress.sinon.match.any, 3) + + // match any value from a list + expect(spy).to.be.calledWith(Cypress.sinon.match.in([1, 2, 3]), 3) + + /** + * Returns true if the given number is event + * @param {number} x + */ + const isEven = (x) => x % 2 === 0 + + // expect the value to pass a custom predicate function + // the second argument to "sinon.match(predicate, message)" is + // shown if the predicate does not pass and assertion fails + expect(spy).to.be.calledWith(Cypress.sinon.match(isEven, 'isEven'), 3) + + /** + * Returns a function that checks if a given number is larger than the limit + * @param {number} limit + * @returns {(x: number) => boolean} + */ + const isGreaterThan = (limit) => (x) => x > limit + + /** + * Returns a function that checks if a given number is less than the limit + * @param {number} limit + * @returns {(x: number) => boolean} + */ + const isLessThan = (limit) => (x) => x < limit + + // you can combine several matchers using "and", "or" + expect(spy).to.be.calledWith( + Cypress.sinon.match.number, + Cypress.sinon.match(isGreaterThan(2), '> 2').and(Cypress.sinon.match(isLessThan(4), '< 4')) + ) + + expect(spy).to.be.calledWith( + Cypress.sinon.match.number, + Cypress.sinon.match(isGreaterThan(200), '> 200').or(Cypress.sinon.match(3)) + ) + + // matchers can be used from BDD assertions + cy.get('@add').should('have.been.calledWith', + Cypress.sinon.match.number, Cypress.sinon.match(3) + ) + + // you can alias matchers for shorter test code + const { match: M } = Cypress.sinon + + cy.get('@add').should('have.been.calledWith', M.number, M(3)) + }) }) diff --git a/package-lock.json b/package-lock.json index 41fec152f..32ab8a362 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1428,6 +1428,26 @@ "colors": "1.0.3" } }, + "cli-table3": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.5.1.tgz", + "integrity": "sha512-7Qg2Jrep1S/+Q3EceiZtQcDPWxhAvBw+ERf1162v4sikJrvojMHFqXt8QIVha8UlH9rgU0BeWPytZ9/TzYqlUw==", + "dev": true, + "requires": { + "colors": "^1.1.2", + "object-assign": "^4.1.0", + "string-width": "^2.1.1" + }, + "dependencies": { + "colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", + "dev": true, + "optional": true + } + } + }, "cli-truncate": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-0.2.1.tgz", @@ -1837,9 +1857,9 @@ } }, "cypress": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/cypress/-/cypress-4.1.0.tgz", - "integrity": "sha512-FFV8pS9iuriSX4M9rna6awJUhiqozZD1D5z5BprCUJoho1ctbcgpkEUIUnqxli2OwjQqVz07egO+iqoGL+tw7g==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/cypress/-/cypress-4.2.0.tgz", + "integrity": "sha512-8LdreL91S/QiTCLYLNbIjLL8Ht4fJmu/4HGLxUI20Tc7JSfqEfCmXELrRfuPT0kjosJwJJZacdSji9XSRkPKUw==", "dev": true, "requires": { "@cypress/listr-verbose-renderer": "0.4.1", @@ -1850,6 +1870,7 @@ "cachedir": "2.3.0", "chalk": "2.4.2", "check-more-types": "2.24.0", + "cli-table3": "0.5.1", "commander": "4.1.0", "common-tags": "1.8.0", "debug": "4.1.1", @@ -1865,12 +1886,12 @@ "listr": "0.14.3", "lodash": "4.17.15", "log-symbols": "3.0.0", - "minimist": "1.2.0", + "minimist": "1.2.2", "moment": "2.24.0", "ospath": "1.2.2", "pretty-bytes": "5.3.0", "ramda": "0.26.1", - "request": "2.88.0", + "request": "github:cypress-io/request#b5af0d1fa47eec97ba980cde90a13e69a2afcd16", "request-progress": "3.0.0", "supports-color": "7.1.0", "tmp": "0.1.0", @@ -1924,12 +1945,45 @@ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, + "minimist": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.2.tgz", + "integrity": "sha512-rIqbOrKb8GJmx/5bc2M0QchhUouMXSpd1RTclXsB41JdL+VtnojfaJR+h7F9k18/4kHUsBFgk80Uk+q569vjPA==", + "dev": true + }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, + "request": { + "version": "github:cypress-io/request#b5af0d1fa47eec97ba980cde90a13e69a2afcd16", + "from": "github:cypress-io/request#b5af0d1fa47eec97ba980cde90a13e69a2afcd16", + "dev": true, + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + } + }, "supports-color": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", @@ -1938,6 +1992,16 @@ "requires": { "has-flag": "^4.0.0" } + }, + "tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "dev": true, + "requires": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + } } } }, diff --git a/package.json b/package.json index 82a5906f0..6a6534b60 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "cy:verify": "cypress verify", "cy:info": "cypress info", "cy:version": "cypress version", + "cy:cache:list": "cypress cache list", "cy:run": "cypress run", "cy:run:record": "cypress run --record", "cy:open": "cypress open", @@ -62,7 +63,7 @@ "devDependencies": { "@bahmutov/print-env": "1.2.0", "colon-names": "1.0.0", - "cypress": "4.1.0", + "cypress": "4.2.0", "eslint": "5.16.0", "eslint-plugin-cypress": "2.8.1", "eslint-plugin-cypress-dev": "2.1.0",