End to end testing is one of the testing methodologies which is supposed to check whether if an application works as expected or not, by testing the so called user flow. Well let's write couple of e2e tests for one of the React applications that I've made: , a simple utility with which you can convert CSS snippets to React Native/JSS stylesheet objects with ease. Please read through the behind story . csstox here Getting started As evident from the title we would be making use of Cypress as the testing framework. First up we need to have cypress installed as a devDependency. yarn add -D cypress The following command creates couple of files and directories as needed by Cypress. ./node_modules/.bin/cypress open Alternatively use the shortcut npm bin /cypress $( ) npm bin open After couple of tweaks the directory structure for the test setup looks like the one below: tests └── e2e ├── integration │ ├── basic.spec.js │ └── behavior.spec.js └── screenshots Next up we need to configure Cypress based on the changes made, and we've got `cypress.json` file for this purpose. Let's make Cypress aware that it has to search for the intended files within directory: tests/e2e/integration : , "integrationFolder" "tests/e2e/integration" A final version would look like the one below: { : , : , : , : , : , : } // cypress.json "baseUrl" "http://localhost:3000" "integrationFolder" "tests/e2e/integration" "screenshotsFolder" "tests/e2e/screenshots" "supportFile" false "pluginsFile" false "video" false Also, Cypress would require our application to be up and running prior to start executing the tests. Let's install a utility that would do this for us. yarn add -D start-server-and-test Let's go ahead and add the following scripts to `package.json` : , : "cy:run" "cypress open" "test:e2e" "start-server-and-test :3000 cy:run" With that we can launch the test setup with ` `. yarn run test:e2e by default looks for a start script, luckily this is the case for us. Or else we're required to provide the associated script name as the very first argument followed by the local server URL and test script. And we're all set to start writing tests for our app. start-server-and-test As you might have noticed from the directory structure above, there are two test suites: 1. Basic Workflow - This one's kinda like smoke-tests, it ensures that things are ready to carry out further test cases. - Behavior - It includes test cases that ensures the end to end behavior of the application. Basic workflow First up we need to ensure if our app is up and running. it( , () => { cy.visit( ); }); "renders without crashing" "/" 2. We've a select box as part of the UI which defaults to the value - 'React Native'. Cypress provides various commands to interact with the DOM as a real user would do. Here, we need a utility that would pick up the select box and makes sure that it defaults to the value, 'React Native'. it( , () => { cy.visit( ) .get( ) .should( , ); }); "expects to find the select box defaulting to React Native" "/" "[data-testid=selectbox]" "have.value" "React Native" As you might have noticed an attribute ( ) selector is used instead of a class selector, you might be wondering why. There're couple of best practices listed in the Cypress docs website and you can find to be one among them. data-testid selecting elements CSS classes are subjected to change anytime causing the test case to fail which would not be the case with `data` attributes. As expected we've refactored the to have a attribute. respective component data-testid Cypress comes up with a handful of assertions to choose from which are made available from assertion libraries such as , etc. One can create an assertion with , and now we've have a better picture. chai sinon should() Behavior Hurray, we've just finished writing test cases for the first test suite. And now we're off to write tests that describe the behavior of the app in detail. 1. We've two elements which serves different purpose. The one on the left is supposed to allow the user paste a CSS snippet while the other one should be displaying the React Native/JSS equivalent of the same. This calls for the need to type some input CSS snippet to the respective element. Luckily we've a command as provided by Cypress for the purpose. textarea textarea type() it( , () => { cssSnippet = ; cy.visit( ) .get( ) .type(cssSnippet) .should( , cssSnippet); }); "is possible to enter text to the `textarea` intended to receive input CSS snippet" const "padding: 10px;" "/" "[data-testid=input]" "have.value" 2. As said before both the elements perform different roles. The one on the right is supposed to display the React Native/JSS equivalent which should be made not editable by the user. How're we gonna write a test case for this scenario? Well, it's pretty simple. Just make sure that the respective element has got a property. textarea textarea readonly it( , () => { cy.visit( ).get( ).should( , ); }); "expects to find readonly attribute associated with the textarea intended to display the result" "/" "[data-testid=output]" "have.attr" "readonly" 3. And now we need to write a test case to ensure if the application serves it's purpose, that is if an input CSS snippet is being converted to the respective equivalent. it( , () => { inputCSSRule = ; result = { : [{ : }, { : }, { : }], }; cy.visit( ) .get( ) .type(inputCSSRule) .get( ) .should( , .stringify(result, , )); }); "converts an input CSS snippet to the React Native equivalent" const "transform: translate(10px, 5px) scale(5);" const transform scale 5 translateY 5 translateX 10 "/" "[data-testid=input]" "[data-testid=output]" "have.value" JSON null 2 4. Here comes the JSS counterpart presenting before us a new challenge. The select box defaults to the value - 'React Native', we're required to change the value to JSS and Cypress comes to the rescue with . select() it( , () => { inputCSSRule = ; result = { : , }; cy.visit( ) .get( ) .select( ) .get( ) .type(inputCSSRule) .get( ) .should( , .stringify(result, , )); }); "converts an input CSS snippet to the JSS equivalent" const "margin: 5px 7px 2px;" const margin "5px 7px 2px" "/" "[data-testid=selectbox]" "JSS" "[data-testid=input]" "[data-testid=output]" "have.value" JSON null 2 5. We've validations in place to ensure submitting an invalid CSS rule results in an appropriate warning being displayed in the output element. Well, let's write a test case for it. textarea it( , () => { inputCSSRule = ; result = ; cy.visit( ) .get( ) .type(inputCSSRule) .get( ) .should( { expect(el).to.contain(result); }); }); "shows an error message for invalid CSS snippet" const "margin: 5" const `Error translating CSS` "/" "[data-testid=input" "[data-testid=output]" ( ) => el 6. If the input element is left blank we've a placeholder in place and the equivalent version is displayed on the output element. textarea textarea it( , () => { result = { : , : , : , }; cy.visit( ) .get( ) .should( { expect(el).to.contain.text( .stringify(result, , )); }); }); "generates the React Native equivalent of default CSS rule available as placeholder" const fontSize 18 lineHeight 24 color "red" "/" "[data-testid=output]" ( ) => el JSON null 2 7. And the JSS counterpart. it( , () => { result = { : , : , : , }; cy.visit( ) .get( ) .select( ) .get( ) .should( { expect(el).to.contain.text( .stringify(result, , )); }); }); "generates the JSS equivalent of default CSS rule available as placeholder" const fontSize "18px" lineHeight "24px" color "red" "/" "[data-testid=selectbox]" "JSS" "[data-testid=output]" ( ) => el JSON null 2 And that's pretty much it. We have gone through only a few things that Cypress offers, please get to know more from the . Thanks for reading through. official docs If you wish to catch up with my work, follow me on . twitter Previously published at https://dev.to/jamesgeorge007/let-s-write-e2e-tests-for-a-react-application-with-cypress-4a8i