Back to Repositories

Testing Vue CLI Development Server Functionality in vue-cli

This test suite validates the Vue CLI service’s development server functionality, covering hot module replacement (HMR), router integration, and various serve configurations. It ensures reliable development server behavior across different Vue.js application setups.

Test Coverage Overview

The test suite provides comprehensive coverage of Vue CLI’s serve functionality:
  • Basic serve functionality with hot reload capability
  • Router integration testing with both modern and legacy configurations
  • Vuex state management validation
  • Custom entry point handling
  • Public directory absence scenarios
  • WebSocket connection efficiency for HMR

Implementation Analysis

The testing approach utilizes Jest and Puppeteer for end-to-end testing of the development server. It implements project creation utilities and custom serve helpers to validate Vue.js application behavior in a controlled environment.

Key patterns include async/await for handling asynchronous operations, custom helper functions for DOM manipulation, and timeout management for slower environments.

Technical Details

  • Testing Framework: Jest with extended timeout (80000ms)
  • Browser Automation: Puppeteer
  • Custom Utilities: @vue/cli-test-utils
  • Test Environment: Simulated development server
  • Configuration: Various preset configurations including router and Vuex

Best Practices Demonstrated

The test suite exemplifies several testing best practices:
  • Isolated test environments for each scenario
  • Proper error handling and environment-specific accommodations
  • Consistent wait patterns for async operations
  • Comprehensive feature coverage
  • Clean test organization with descriptive test cases

vuejs/vue-cli

packages/@vue/cli-service/__tests__/serve.spec.js

            
jest.setTimeout(80000)

const path = require('path')
const fs = require('fs-extra')
const { defaultPreset } = require('@vue/cli/lib/options')
const create = require('@vue/cli-test-utils/createTestProject')
const serve = require('@vue/cli-test-utils/serveWithPuppeteer')

const sleep = n => new Promise(resolve => setTimeout(resolve, n))
test('serve', async () => {
  const project = await create('e2e-serve', defaultPreset)

  await serve(
    () => project.run('vue-cli-service serve'),
    async ({ page, nextUpdate, helpers }) => {
      const msg = `Welcome to Your Vue.js App`
      expect(await helpers.getText('h1')).toMatch(msg)

      // test hot reload
      const file = await project.read(`src/App.vue`)
      project.write(`src/App.vue`, file.replace(msg, `Updated`))
      await nextUpdate() // wait for child stdout update signal
      try {
        await page.waitForFunction(selector => {
          const el = document.querySelector(selector)
          return el && el.textContent.includes('Updated')
        }, { timeout: 60000 }, 'h1')
      } catch (e) {
        if (process.env.APPVEYOR && e.message.match('timeout')) {
          // AppVeyor VM is so slow that there's a large chance this test cases will time out,
          // we have to tolerate such failures.
          console.error(e)
        } else {
          throw e
        }
      }
    }
  )
})
test('serve with router', async () => {
  const project = await create('e2e-serve-router', Object.assign({}, defaultPreset, {
    plugins: {
      '@vue/cli-plugin-router': {}
    }
  }))

  await serve(
    () => project.run('vue-cli-service serve'),
    async ({ page, helpers }) => {
      expect(await helpers.getText('h1')).toMatch(`Welcome to Your Vue.js App`)
      expect(await helpers.hasElement('nav')).toBe(true)
      expect(await helpers.hasClass('a[href="#/"]', 'router-link-exact-active')).toBe(true)
      expect(await helpers.hasClass('a[href="#/about"]', 'router-link-exact-active')).toBe(false)

      await page.click('a[href="#/about"]')
      await sleep(1000)
      expect(await helpers.getText('h1')).toMatch(`This is an about page`)
      expect(await helpers.hasElement('nav')).toBe(true)
      expect(await helpers.hasClass('a[href="#/"]', 'router-link-exact-active')).toBe(false)
      expect(await helpers.hasClass('a[href="#/about"]', 'router-link-exact-active')).toBe(true)
    }
  )
})

test('serve with legacy router option', async () => {
  const project = await create('e2e-serve-legacy-router', Object.assign({}, defaultPreset, {
    router: true,
    routerHistoryMode: true
  }))

  await serve(
    () => project.run('vue-cli-service serve'),
    async ({ page, helpers }) => {
      expect(await helpers.getText('h1')).toMatch(`Welcome to Your Vue.js App`)
      expect(await helpers.hasElement('nav')).toBe(true)
      expect(await helpers.hasClass('a[href="/"]', 'router-link-exact-active')).toBe(true)
      expect(await helpers.hasClass('a[href="/about"]', 'router-link-exact-active')).toBe(false)

      await page.click('a[href="/about"]')
      await sleep(1000)
      expect(await helpers.getText('h1')).toMatch(`This is an about page`)
      expect(await helpers.hasElement('nav')).toBe(true)
      expect(await helpers.hasClass('a[href="/"]', 'router-link-exact-active')).toBe(false)
      expect(await helpers.hasClass('a[href="/about"]', 'router-link-exact-active')).toBe(true)
    }
  )
})

test('serve with legacy vuex option', async () => {
  const project = await create('e2e-serve-legacy-vuex', Object.assign({}, defaultPreset, {
    vuex: true
  }))

  await serve(
    () => project.run('vue-cli-service serve'),
    async ({ page, helpers }) => {
      expect(await helpers.getText('h1')).toMatch(`Welcome to Your Vue.js App`)
    }
  )
})

test('serve with inline entry', async () => {
  const project = await create('e2e-serve-inline-entry', defaultPreset)

  await fs.move(
    path.resolve(project.dir, 'src/main.js'),
    path.resolve(project.dir, 'src/index.js')
  )

  await serve(
    () => project.run('vue-cli-service serve src/index.js'),
    async ({ page, nextUpdate, helpers }) => {
      const msg = `Welcome to Your Vue.js App`
      expect(await helpers.getText('h1')).toMatch(msg)

      // test hot reload
      const file = await project.read(`src/App.vue`)
      project.write(`src/App.vue`, file.replace(msg, `Updated`))
      await nextUpdate() // wait for child stdout update signal
      try {
        await page.waitForFunction(selector => {
          const el = document.querySelector(selector)
          return el && el.textContent.includes('Updated')
        }, { timeout: 60000 }, 'h1')
      } catch (e) {
        if (process.env.APPVEYOR && e.message.match('timeout')) {
          // AppVeyor VM is so slow that there's a large chance this test cases will time out,
          // we have to tolerate such failures.
          console.error(e)
        } else {
          throw e
        }
      }
    }
  )
})

test('serve with no public dir', async () => {
  const project = await create('e2e-serve-no-public', defaultPreset)

  await fs.remove(path.resolve(project.dir, 'public'))

  await serve(
    () => project.run('vue-cli-service serve'),
    async ({ page, nextUpdate, helpers }) => {
      const msg = `Welcome to Your Vue.js App`
      expect(await helpers.getText('h1')).toMatch(msg)

      // test hot reload
      const file = await project.read(`src/App.vue`)
      project.write(`src/App.vue`, file.replace(msg, `Updated`))
      await nextUpdate() // wait for child stdout update signal
      try {
        await page.waitForFunction(selector => {
          const el = document.querySelector(selector)
          return el && el.textContent.includes('Updated')
        }, { timeout: 60000 }, 'h1')
      } catch (e) {
        if (process.env.APPVEYOR && e.message.match('timeout')) {
          // AppVeyor VM is so slow that there's a large chance this test cases will time out,
          // we have to tolerate such failures.
          console.error(e)
        } else {
          throw e
        }
      }
    }
  )
})

test('use a single websocket connection for HMR', async () => {
  const project = await create('e2e-serve-hmr', defaultPreset)

  await serve(
    () => project.run('vue-cli-service serve'),
    async ({ helpers, requestUrls }) => {
      const msg = `Welcome to Your Vue.js App`
      expect(await helpers.getText('h1')).toMatch(msg)

      expect(requestUrls.filter(url => url.includes('ws://')).length).toBe(1)
    }
  )
})