When it comes to writing tests, it’s not just about making them work; it’s also essential to ensure they are well-organized, easy to maintain, and efficient. In this post, I’ll share my journey of refactoring messy tests into a cleaner and more maintainable state.

Organize Test Configuration

The first step is to organize my test configuration. It’s crucial to keep my tests modular and separate the setup from the actual test cases. I started by running rspec --init, which generated a spec_helper.rb file, a central place for configuring my tests. Instead of cluttering my spec files with numerous require statements, I moved them to the spec_helper.rb. This significantly improved the load time of my tests.

Here’s how my spec_helper.rb looked after the changes:

require 'bundler/setup'
Bundler.require

Dir["./*.rb"].each { |file| require file }
Dir["./app/**/*.rb"].each { |file| require file }

Dotenv.load(".env.test")

This approach allows me to manage gems and require files more efficiently and reduces the clutter in my individual spec files.

Simplify Gem Management

Managing gems efficiently is crucial for test stability and maintainability. Using Bundler to require my gems simplifies gem management. Here’s a breakdown of this step:

  • Using Bundler: Bundler.require allows me to load all the gems specified in my Gemfile. It ensures that all necessary gems are available for my tests without the need to require them individually.

  • Consistency: It maintains consistency in gem management across my project, reducing the likelihood of gem-related issues.

  • Dependency Management: Bundler helps manage gem dependencies, ensuring that you use compatible gem versions, which is essential for maintaining a stable testing environment.

Improve Naming Cohesion

Consistency in naming and coding style is essential for making my tests readable and maintainable. In this step, I decide to use let consistently throughout my tests:

  • Rationale for let: let is a feature provided by RSpec for defining memoized helper methods in my test examples. It’s a good choice when you want to share values across multiple examples within the same example group and ensure that the value is only calculated once.

  • Benefits of let: Using let consistently throughout my tests makes my code more readable and efficient. It avoids redundant calculations and aligns with common practices in the RSpec community.

  • Naming Cohesion: Consistency in how you define and name variables (e.g., using let everywhere) ensures that my code is more cohesive and easier for other developers to understand.

Manage the Database with Database Cleaner

One common challenge in testing is managing the database state. While some frameworks, like Rails, provide built-in tools for this, I needed a solution for my non-framework project. To address this, I added the database_cleaner-active_record gem to my project.

In my spec_helper.rb, I set up DatabaseCleaner to handle cleaning the database before each test. Here’s a snippet of the code:

config.before(:each) do
  DatabaseCleaner.strategy = :truncation
  DatabaseCleaner.clean_with(:deletion)
end

config.around(:each) do |example|
  DatabaseCleaner.cleaning do
    example.run
  end
end

This configuration ensures that the database is in a clean state before and after each test, making it easier to write and maintain database-related test cases.

But, there is one small issue. I have tests where I check how multitenancy work. For this I switch between active record connections. I made small hack for that:

config.before(:each) do
    DatabaseCleaner.strategy = :truncation
    DatabaseCleaner.clean_with(:deletion)
  rescue ActiveRecord::ConnectionNotEstablished
    next
  end

  config.around(:each) do |example|
    DatabaseCleaner.cleaning do
      example.run
    end
  rescue ActiveRecord::ConnectionNotEstablished
    next
  end

I didn’t found better solution yet. But if someone has better solution, please inform me.

Avoid Manually Setting Environment Variables

Setting environment variables for tests can be a hassle. I eliminated the need to set environment variables manually by using Dotenv. By loading my test environment variables from a .env.test file in the spec_helper.rb, I made it easier to configure the test environment.

Centralize ActiveRecord Setup

If your tests involve ActiveRecord, it’s a good practice to centralize the setup. Instead of repeating the configuration in each spec file, I moved the ActiveRecord configuration to my spec_helper.rb. This ensures that the database configuration is consistent across all test cases.

Measure Test Coverage with SimpleCov

To keep track of test coverage, I added the simplecov gem to my project. This gem provides a clear overview of code coverage, highlighting areas that need more testing. By including SimpleCov in my spec_helper.rb and running tests, I could easily monitor code coverage and ensure that my tests cover a significant portion of the codebase.

Refactoring messy tests may seem daunting, but the benefits of cleaner and more maintainable tests are well worth the effort. By following these steps, you can significantly improve the quality and efficiency of my test suite. Whether you’re working on a small project or a large codebase, organized and readable tests will save you time and make my development process smoother. Check the code on github. And if you have any question or ideas, contact with me.