Chapter 3.1.1: Basic test setup with Cypress and Vitest: directory structure
This chapter will focus on setting up a standard Vue project with Vite and adding a basic configuration for testing. First we start with the directory structure.
Disclaimer: You are reading an early version of the text! The final version of the book will be revised and may contain additional content.
We'll start with the basics before we dig deeper into what additional tools and configuration steps we need to meet all the requirements for a perfect setup. This chapter will focus on setting up a standard Vue project with Vite and adding a basic configuration for testing. For this purpose, we will install Cypress and Vitest. For both frameworks, we will place all related files, such as configuration files and plugins, in a central location in the file tree of our project. In the first step, we assume that we will use Cypress exclusively for application tests and Vitest only for unit tests. Later in the book, we will extend the test setup to use both frameworks for both types of tests. Likewise, we will build on the base setup and extend it to cover the three principles defined earlier in the best possible way.
So let’s start with the initial setup: We will install and configure the required dependencies step by step. You can take a shortcut and download the demo project from the following URL: https://github.com/maoberlehner/perfect-test-setup/tree/base-setup (paid subscribers and book owners only; please get in touch with me if this is you and you want access). However, I still recommend at least skimming the upcoming section, as we will already take a closer look at some crucial aspects during the basic setup as well.
Disclaimer: I created the following examples in this book using Node v16, npm v8, and the current (as of May 7, 2022) versions of the respective npm dependencies. Depending on when you read this, some adaptations may be necessary. I ask for your understanding.
We use a Vite project with TypeScript as the basis for our perfect test setup:
# Use your preferred project name instead of `good-tests`
npm create vite@latest good-tests -- --template vue-ts
cd good-tests
npm install
After the initial setup of our brand new Vue application, we can proceed to install the test frameworks of our choice and a couple of other dependencies:
npm install --save-dev cypress vitest happy-dom c8 @vue/test-utils
In the first step, we will use cypress
for application tests, and vitest
is our preferred tool for unit tests. Where by unit tests, we mean tests for JavaScript classes and functions as well as Vue component tests. We are using the happy-dom
package in combination with Vitest to simulate a DOM environment under Node.js. c8
is a package that allows us to measure how well our code is covered by tests. Finally, the @vue/test-utils
help us to test Vue components.
Now we can start configuring the test frameworks. By default, the configuration files for the two frameworks are located directly in an application's root directory. However, this quickly becomes messy the more configuration files accumulate in a project. I, therefore, prefer to move the configuration files into the tests
directory. And within the tests directory, we also want to move all files that are specific to a particular framework into a subdirectory again. To do this, we create separate folders for Cypress and Vitest, respectively. Doing so makes it much easier for us to swap test frameworks in the future if necessary. We can find all files related to a particular test framework in the corresponding folder. For example, if we decide to switch from Vitest back to Jest, we only need to delete the corresponding folder, and we can be sure that all traces of Vitest are gone from our codebase.
├─ ...
├─ tests
│ ├─ driver
│ │ ├─ cypress
│ │ │ ├─ plugins
│ │ │ │ └─ index.ts
│ │ │ ├─ support
│ │ │ │ └─ index.ts
│ │ │ ├─.gitignore
│ │ │ ├─ cypress.config.json
│ │ │ └─ tsconfig.json
│ │ └─ vitest
│ │ │ ├─.gitignore
│ │ │ └─ vitest.config.json
│ ├─ specs
│ └─ tsconfig.json
└─ ...
Above, we see the file tree of the tests
folder in our project. Let's start with something simple: tests/driver/cypress/plugins/index.ts
.
// tests/driver/cypress/plugins/index.ts
export default () => {
// configure plugins here
};
This file serves simply as a placeholder for now. When we are ready, we can add plugins here. Similarly, with tests/driver/cypress/support/index.ts
, we can leave this file empty for now. All that matters is that these two files exist because we also reference them in cypress.config.json
. Later we will come back to these files.
The file tests/driver/cypress/.gitignore
contains only two lines:
# tests/driver/cypress/.gitignore
screenshots
videos
Cypress automatically creates screenshots and videos when tests fail. Having a screenshot or a video can be enormously helpful when figuring out why a test is failing. However, we don't want these automatically generated files in our Git repository. We can prevent this by adding these two lines to the .gitignore
file.
Next, let's look at the cypress.config.json
file:
{
"baseUrl": "http://localhost:3000",
"componentFolder": "src/components",
"downloadsFolder": "tests/driver/cypress/downloads",
"fixturesFolder": "tests/driver/cypress/fixtures",
"integrationFolder": "tests",
"pluginsFile": "tests/driver/cypress/plugins/index.ts",
"screenshotsFolder": "tests/driver/cypress/screenshots",
"supportFile": "tests/driver/cypress/support/index.ts",
"videosFolder": "tests/driver/cypress/videos",
"testFiles": "**/specs/**/*.spec.ts"
}
Essentially, this is where we tell Cypress where to find all mandatory folders and files. If you decide to use a different folder structure, this is the place to adjust the paths accordingly.
Now we move on to the tests/driver/cypress/tsconfig.json
file. Here we can make settings for TypeScript that only take effect in this directory and its subdirectories.
{
"extends": "../../../tsconfig.json",
"compilerOptions": {
"types": [
"cypress"
]
}
}
We want to inherit all the settings from our global configuration file. Therefore we use the extends
option. In addition, all Cypress relevant types for TypeScript should be available in this directory. We achieve this with the compilerOptions
setting. Of course, we could also apply this configuration in the global tsconfig.json
file. But this way, the Cypress-specific configuration is cleanly separated from the rest of our application.
In the global tsconfig.json
, we explicitly ignore the tests
folder! This is because we want to strictly separate the TypeScript configuration of our application code from the test code. Therefore we need a second tsconfig.json
file: tests/tsconfig.json
.
{
"extends": "../tsconfig.json",
"include": ["**/*.ts", "**/*.d.ts"],
}
Here we extend the tsconfig.json
file from the root directory and include all relevant TypeScript files in the tests
directory and its subdirectories.
There is also a .gitignore
file in the vitest
driver folder. In it, we specify that the coverage
folder, which Vitest creates when we measure code coverage, should not be added to our Git repository.
# tests/driver/vitest/.gitignore
coverage
Now the only thing missing is the vitest.config.ts
file. It contains all settings for the Vitest framework.
// tests/driver/vitest/vitest.config.ts
import { defineConfig } from 'vitest/config';
import viteConfig from '../../../vite.config';
export default defineConfig({
...viteConfig,
test: {
environment: `happy-dom`,
include: [`./src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}`],
coverage: {
reportsDirectory: `./tests/driver/vitest/coverage`,
},
},
});
Since Vitest uses Vite under the hood, we can conveniently reuse the Vite configuration of our project. This is a massive advantage over Jest and other frameworks. All the plugins and settings necessary to bundle our application are thus automatically available in our test runner. We only need to specify happy-dom
as environment
and use the include
option to specify where Vitest should look for test files.
A major goal of our test setup is to be able to interchange the technologies used as conveniently as possible without requiring a complete rewrite of all tests. Moving all files concerning a particular test framework into a separate folder goes a long way towards achieving this goal.
So much for the basic initial setup of Cypress and Vitest. In the following article, we will add a watch mode to Cypress, localize Cypress helper functions, and, ultimately write our first two tests.
What is your experience with setting up test frameworks? Do you have a trick up your sleeves that you want to share with us?
Another question on a small detail: Why do you prefer to use the word "driver" for your testing framework directory?
Markus, you refer to "application tests" when applying Cypress. Are you referring more to E2E tests or Acceptance tests, seeing that Chapter two focussed on Acceptance and Unit tests, but Cypress doesn't use the term "acceptance tests" in their documentation?
I'm referencing your articles of 2018 where you are applying Cypress to do acceptance-like tests:
Part 1: https://markus.oberlehner.net/blog/automated-acceptance-testing-with-cypress-and-vue-setup/
Part 2: https://markus.oberlehner.net/blog/automated-acceptance-testing-with-cypress-and-vue-network-stubs-and-timers/