Traditionally, in functional testing, a clean up/tear down script would be run in something called an After hook. These hooks are part of the Cucumber DSL and are designed to execute when a scenario has finished.

Cucumber example:

After do |scenario|
  if scenario.failed?
    MultiLogger.clean_logs
  end
end

There is a drawback to these hooks where they will fail to run when certain exit codes are returned from the scenario or the program is interrupted. This isn’t great when you need these scripts to execute on every run, regardless of the exit reason.

This is where at_exit comes in.

The at_exit function

The at_exit function is defined in the Kernel module within the Ruby class Object. This makes their methods available in every Ruby Object. This is ideal as we have no complex dependencies to consider when using it.

at_exit works when a program exits. It guarantees to run at exit regardless of the reason; be it a successful program execution, an uncaught exception or someone manually killing the program (ctrl + c).

It works by converting a given block to a Proc and registers it for execution when the program exits.

Example:

Here’s how this function is used in the TYR project to clear our Mailsac account of all temporary inboxes created within the tests.

at_exit do
  list_inboxes.map { |i| i['_id'] }
              .grep(/tyr-e2e-*/)
              .then { |i| i.each { |inbox| delete_inbox(inbox: inbox) }}
end

Conclusion

For a reliable solution to execute a function/script at program exit in Ruby, the at_exit function is the most reliable to use.