Skip to main content

Testing Vagrant Environments

1. Using Test Kitchen with Vagrant

Example: Basic Test Kitchen Configuration

# kitchen.yml
---
driver:
  name: vagrant

provisioner:
  name: chef_zero

platforms:
  - name: ubuntu-20.04
    driver_config:
      box: "ubuntu/focal64"

suites:
  - name: default
    run_list:
      - recipe[myapp::default]
    attributes:
Component Description Notes
driver.name vagrant Specifies Vagrant as the driver
provisioner.name chef_zero Uses Chef Zero for provisioning
platforms[].name ubuntu-20.04 Target OS for testing
platforms[].driver_config.box ubuntu/focal64 Vagrant box to use for testing
suites[].name default Name of the test suite
Note: Test Kitchen uses a kitchen.yml configuration file to define test environments and provisioners.
Warning: Ensure Vagrant is installed and the required box is available before running Test Kitchen.

Key Features

  • Supports multiple platforms (Ubuntu, CentOS, Windows)
  • Integrates with Chef, Puppet, Ansible
  • Automates environment setup and testing
  • Provides isolated test environments

2. Running ServerSpec Tests

Example: Basic Serverspec Test

require 'spec_helper'

describe 'nginx' do
  it { is_expected.to be_installed }
  it { is_expected.to be_running }
end

describe 'port 80' do
  it { is_expected.to be_listening.on('80') }
end
Test Type Description Usage
be_installed Checks if a package is installed describe 'nginx' do it { is_expected.to be_installed } end
be_running Checks if a service is running describe 'nginx' do it { is_expected.to be_running } end
be_listening Checks if a port is listening describe 'port 80' do it { is_expected.to be_listening.on('80') } end
Note: ServerSpec tests are typically written in Ruby and executed within the Vagrant environment.
Warning: Ensure that the Vagrant box has the necessary Ruby environment and Serverspec gem installed.

ServerSpec Test Execution Steps

  • Write test files in spec/ directory
  • Run tests using vagrant ssh and rspec
  • Use vagrant provision to apply changes before testing

3. Implementing InSpec Integration

Example: Basic InSpec Profile Structure

my_vagrant_test/
├── inspec.yml
├── controls/
│   └── vagrant_setup.rb
└── attributes/
    └── default.json
Field Description Example
name Name of the InSpec profile "vagrant-test"
title Human-readable title of the profile "Vagrant Environment Tests"
version Version of the profile "1.0.0"
maintainer Profile maintainer name "DevOps Team"
license License for the profile "Apache-2.0"
summary Short description of the profile "Tests for Vagrant environment setup"

Example: InSpec Control to Check SSH Access

control "ssh-access" do
  impact 1.0
  title "SSH access should be enabled"
  desc "Ensure SSH is accessible on the Vagrant box"

  describe port(22) do
    it { should be_listening }
  end

  describe service("ssh") do
    it { should be_enabled }
    it { should be_running }
  end
end

Example: InSpec Control to Validate File Permissions

control "file-permissions" do
  impact 0.8
  title "Validate application directory permissions"
  desc "Ensure app directory has correct permissions"

  describe file("/var/www/app") do
    it { should be_directory }
    its("mode") { should cmp "0755" }
  end
end
Note: InSpec tests can be run locally or integrated into CI pipelines using vagrant provision with a test runner.

InSpec Integration Workflow

  • Write InSpec controls to validate Vagrant environment state
  • Run tests manually using inspec exec .
  • Integrate with Vagrant by adding inspec to the provisioner list
  • Use vagrant validate to check Vagrantfile syntax before running tests

4. Using BATS for Shell Testing

Example: Basic BATS Test Setup

# Create test directory structure
mkdir -p /vagrant/tests
cd /vagrant/tests

# Initialize BATS test file
cat > test_example.bats << 'EOF'
#!/usr/bin/env bats

@test "check if service is running" {
  run systemctl is-active myservice
  [ "$status" -eq 0 ]
}

@test "verify configuration file exists" {
  [ -f /etc/myapp/config.conf ]
}
EOF

# Make test file executable
chmod +x test_example.bats
Feature Description Notes
BATS Bash Automated Testing System Designed for testing shell scripts and commands
test file naming *.bats Standard extension for BATS test files
test execution bats test_file.bats Run tests using the bats command
Note: BATS tests run in a clean environment, so ensure all required dependencies are installed in the Vagrant box.

Example: Testing Service Status with BATS

# test_service.bats
#!/usr/bin/env bats

@test "service is running" {
  run systemctl is-active nginx
  [ "$status" -eq 0 ]
}

@test "service logs contain expected message" {
  run journalctl -u nginx --no-pager | grep "started"
  [ "$status" -eq 0 ]
}

Key BATS Concepts

  • @test: Defines a test case with descriptive name
  • run: Executes external commands and captures output
  • $status: Exit code of last executed command
  • $output: Full output from last executed command

5. Creating Custom Test Scripts

Example: Basic Vagrant Test Script

#!/bin/bash
set -e

# Test script for Vagrant environment
echo "Starting Vagrant test..."

# Check if Vagrant is installed
if ! command -v vagrant > /dev/null; then
  echo "Error: Vagrant is not installed"
  exit 1
fi

# Validate Vagrantfile
vagrant validate

# Up the environment
vagrant up

# Run a simple test inside the VM
vagrant ssh -c "echo 'Hello from Vagrant VM'"

# Destroy the environment
vagrant destroy -f

echo "Vagrant test completed successfully"
Script Element Description Usage
#!/bin/bash Shebang to specify the shell interpreter Must be first line of script
set -e Exit immediately if any command fails Ensures test integrity
vagrant validate Validate Vagrantfile syntax Prevents runtime errors
vagrant up Start the Vagrant environment Brings VMs online
vagrant ssh -c "..." Execute command inside VM Test VM functionality
vagrant destroy -f Force destroy Vagrant environment Clean up after tests
Note: Test scripts should be idempotent and clean up after themselves to avoid conflicts in CI/CD pipelines.

6. Testing Provisioning Idempotency

Idempotency in Vagrant provisioning ensures that running the same provisioner multiple times produces the same result without side effects. This is crucial for reliable testing and deployment.

Aspect Description Best Practice
Idempotent Scripts Provisioning scripts should be safe to run multiple times without causing errors or unintended changes. Use conditional checks and idempotent commands (e.g., apt-get install -y package-name is idempotent)
State Management Provisioners must not rely on external state that changes between runs. Use configuration management tools like Ansible or Puppet for better state handling
Vagrantfile Configuration Ensure Vagrantfile does not introduce non-idempotent behavior. Use vagrant provision command to re-run provisioners after changes

Example: Idempotent Shell Script

#!/bin/bash
# Install Node.js if not already installed
if ! command -v node > /dev/null; then
  curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash -
  sudo apt-get install -y nodejs
fi
Note: Idempotency testing is best done by running vagrant provision twice to ensure no errors or unexpected behavior occur on the second run.

7. Validating Multi-Machine Communication

Method Description Example
Vagrant SSH Connect to a specific machine in the multi-machine environment. vagrant ssh machine1
Network Ping Verify network connectivity between machines using ping. ping 192.168.10.10
SSH from One Machine to Another Test communication by SSHing from one machine to another. ssh vagrant@192.168.10.11

Example: Testing Communication Between Machines

vagrant ssh web
ping 192.168.10.10
Note: Ensure that the machines are configured with appropriate network settings in the Vagrantfile to allow communication.

8. Testing Network Configuration

Configuration Description Example
config.vm.network Defines network settings for the VM config.vm.network "private_network", ip: "192.168.56.10"
config.vm.network "private_network", type: "dhcp" Assigns IP via DHCP config.vm.network "private_network", type: "dhcp"
config.vm.network "private_network", ip: "192.168.56.10", auto_config: false Disables automatic network configuration config.vm.network "private_network", ip: "192.168.56.10", auto_config: false

Example: Testing Network Access

vagrant ssh
ping 192.168.56.10

Example: Verifying Port Forwarding

config.vm.network "forwarded_port", guest: 80, host: 8080
Note: Ensure that the host machine allows connections on the forwarded port.

9. Verifying Synced Folder Permissions

Permission Type Description Default Behavior Configuration
synced_folder Defines shared folders between host and guest machines Read-write access for all users config.vm.synced_folder "./host", "/guest"
type Synced folder type (e.g., nfs, rsync) Default is default config.vm.synced_folder "./host", "/guest", type: "nfs"
owner Owner of synced folder on guest machine Defaults to vagrant config.vm.synced_folder "./host", "/guest", owner: "root"
group Group of synced folder on guest machine Defaults to vagrant config.vm.synced_folder "./host", "/guest", group: "root"

Example: Setting Synced Folder Permissions

config.vm.synced_folder "./shared", "/vagrant/shared",
  owner: "www-data",
  group: "www-data"
Note: Ensure that the user running Vagrant has appropriate permissions to access the host directory.
Warning: NFS synced folders may require additional setup on the host machine to ensure proper permissions.

10. Automating Test Execution

Example: Running Tests with Vagrant Provisioning

vagrant up
vagrant provision
Command Description Notes
vagrant up Starts the Vagrant environment Initializes and starts the VM
vagrant provision Re-runs the provisioner Useful for testing changes in configuration

Test Automation Workflow

  • Use vagrant up to initialize the environment
  • Run tests inside the VM using provisioners or scripts
  • Use vagrant provision to re-run tests after configuration changes
Note: Ensure your test scripts are idempotent and can be run multiple times without side effects.

Was this article helpful?