DEV Community

Cover image for Definition Spec in Ruby - implementation in RSpec
Rafał Piekara
Rafał Piekara

Posted on

Definition Spec in Ruby - implementation in RSpec

Imagine this situation. Ruby on Rails project. You are working with your team on a mega-important business feature. You did three days. Everything flashes great. You give it back. Goes into production. Management opens Prosecco, Marketing starts ad words. Complete success 🎉🎉🎉🔥💪.


Meanwhile, an hour after the key release, a colleague from the other team deploys a feature he has been working on for 3 weeks. The feature might have waited another day if it had waited so long. Well, it's going to be in production, because QA was busy testing my key feature, and it's not a business change, some dashboard views and queries, nothing can be spoiled. It will fly straight into production. Pipelines turn green, but ... the application is lying, Puma does not get up. Fuckup. Clients call, support in panic, Prosecco is gasping.

It turns out that by some miracle a code entered the production, in which in one of the classes, at the very end, a small, stray, almost imperceptible letter "s" appeared. Probably a colleague was saving the file, he missed a finger and the letter was written. Nobody noticed during the code review, there was no time to enter the QA, tested in local, but the application crashed.

DefinitionSpec

Sounds weird? Well, no, it's a case study from a project my friend is working on. A real, authentic fact that really happened.


Wait, but how everything worked locally, how pipelines went through since there was a character in the code that should never be there.

Three issues are to blame for this incident:

  • specificity of interpreted languages

  • specificity of Ruby on Rails

  • lack of adequate, minimum test coverage in the project

The specificity of interpreted languages

The difference between interpreted languages and compiled languages is that in interpreted languages, the code is executed on the fly, there is no build phase to allow the compiler to check that the project is at all building. Hence, any typos, syntax errors, and undeclared methods will only come to light when the dirty code fragment is executed. Nobody will warn us sooner.

DefinitionSpec

The specificity of Ruby on Rails

RoR has a lazy loading mechanism in development and test modes. This means that it only loads the classes that are needed to execute the given batch of code. Only in production mode, the entire application code is loaded. As a result, unless there is a bug in the key RoR files responsible for initializing the application, Puma will always stand up in the development environment, and the tests, as long as they do not touch contaminated code, will always pass. The application will not get up on production. Puma won't get up if there is a syntax error in the code, or if a method that is not declared is executing.

No minimum test coverage

It just so happened that the flawed class in this example did not have any tests written.

DefinitionSpec

The other tests did not touch this class, so it gave the impression that nothing was broken and that there is backward compatibility. Tests passed locally, passed in pipelines, and yet it was faulty.

Apparently, every programmer and every programmer worked on at least one project, in which they heard one of three variations: "We do not write tests because"

  • "... the client does not pay for writing tests"

  • "... there's no time, deadlines are chasing."

  • "... it's a simple feature, what to test here."

Who has never had such a fuckup, be the first to throw a stone.

One of my favourite thoughts at work is "Know how is important but the most important is to know how not."

Well, now both me and my friend and his bandmates, and you know "how not". It is time to formulate a short Know How.

If you don't write tests because there is some super important business reason (yes, super important one), it's always worth writing one test that saves your butt in cases like the one above.

I call it the definition spec.

describe MailingService do
  it 'is defined' do
    expect(described_class).not_to be nil
  end
end
Enter fullscreen mode Exit fullscreen mode

It consists in the fact that the first test that I write when creating a new class is checking whether such a class is defined at all, which forces it to be loaded in test mode. These are 4 lines, and they really allow you to ensure a minimum of security and avoid typos. The definition spec in Ruby seems to be a tool tailored to this problem.

Look:

DefinitionSpec

It's not much, but when he sees the green colour, I become calmer.

DefinitionSpec

I created a module to avoid typing the same code every time. You can also use your own matcher. Here are some examples.

Own module:

# spec/support/test_helpers.rb
module TestHelpers
  def definition_spec
    expect(described_class).not_to be nil
  end
end

# w rails_helper.rb
RSpec.configure do |config|
  config.include TestHelpers
end

#Potem w teście
describe PaymentService
  it 'is defined' do
    definition_spec
  end
end

describe SomeClass do
  it 'is defined' do
    definition_spec
  end
end
Enter fullscreen mode Exit fullscreen mode

Test result for red:

DefinitionSpec

Test result for green:

DefinitionSpec

Defining your own matcher

#spec/support/matchers.rb
require 'rspec/expectations'

RSpec::Matchers.define :be_defined do |expected|
  match do |actual|
    !actual.nil?
  end
end

# Potem w teście
describe PaymentService do
  it { is_expected.to be_defined }
end

describe SomeClass do
  it { is_expected.to be_defined }
end
Enter fullscreen mode Exit fullscreen mode

Test result for red:

DefinitionSpec

Test result for green:

DefinitionSpec

In the case of TDD, I guarantee a dopamine incentive. You are writing a definition spec. it's red. You add the file and define the class. It’s Green. And you want to keep working 😃

If you want and have time for it, you can test if the public methods you want to use are defined. No mocking, no extensive scenarios. Minimal coverage that protects against trivial fuckups

Just as startups have the concept of MPV - Minimum Viable Product, I suggest you MVC, but understood as Minimally Verified Class. Something like this can help you introduce a Ruby definition spec into your routine.

Someone wise from the Ruby world (Andrzej Krzywda - you can find a broader context here) said that he works in Ruby because he likes to correct typos in production.

Let's not fix typos in production, let's do it earlier, much earlier.

When adding a definition spec, the tests crash if there is an unwanted letter in the class being tested.

Write definition spec in Ruby, seriously

Discussion (0)