<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Andy Waite</title>
    <description>The latest articles on DEV Community by Andy Waite (@andyw8).</description>
    <link>https://dev.to/andyw8</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F28935%2F081d2774-436f-485a-aa09-2ec4d2d8b31a.jpeg</url>
      <title>DEV Community: Andy Waite</title>
      <link>https://dev.to/andyw8</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/andyw8"/>
    <language>en</language>
    <item>
      <title>A GitHub Actions Rails CI Workflow in 5 lines</title>
      <dc:creator>Andy Waite</dc:creator>
      <pubDate>Fri, 15 Apr 2022 00:00:00 +0000</pubDate>
      <link>https://dev.to/andyw8/a-github-actions-rails-ci-workflow-in-5-lines-230m</link>
      <guid>https://dev.to/andyw8/a-github-actions-rails-ci-workflow-in-5-lines-230m</guid>
      <description>&lt;p&gt;A few years ago, Matt Swanson wrote a &lt;a href="https://boringrails.com/articles/building-a-rails-ci-pipeline-with-github-actions/"&gt;great post&lt;/a&gt; on setting up Rails CI on GitHub Actions. It quickly became my go-to reference for setting up CI for new apps.&lt;/p&gt;

&lt;p&gt;Over time, I made few updates and adjustments to it, so whenever I started a new project I would copy the config from one of my older projects. But this meant each project gradually became inconsistent, and some got updated more than others.&lt;/p&gt;

&lt;p&gt;I wanted to have one single base workflow for all my apps, so that if I made a change, all the apps could easily benefit from it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reusable Workflows
&lt;/h2&gt;

&lt;p&gt;In November 2021, GitHub announced that &lt;a href="https://github.blog/2021-11-29-github-actions-reusable-workflows-is-generally-available/"&gt;Reusable Workflows&lt;/a&gt; was generally available.&lt;/p&gt;

&lt;p&gt;Although GitHub Actions has supported composite actions for a long time, Reusable Workflows allows for a much more concise configuration, and the ability to reference a whole workflow from another repository, rather than having to build up each step individually.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.blog/2022-02-10-using-reusable-workflows-github-actions/"&gt;Read more about it on GitHub’s blog&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introducing setup-rails
&lt;/h2&gt;

&lt;p&gt;I’ve published &lt;strong&gt;&lt;a href="https://github.com/andyw8/setup-rails"&gt;setup-rails&lt;/a&gt;&lt;/strong&gt; as a reusable workflow for quickly and easily enabling CI for Rails apps.&lt;/p&gt;

&lt;p&gt;By creating a single file in your repo, e.g. &lt;code&gt;.github/workflows/verify.yml&lt;/code&gt;, with the contents below, you should a have working CI workflow which configures the database and runs your app’s tests:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name: Verify
on: [push]

jobs:
  verify:
    uses: andyw8/setup-rails/.github/workflows/verify.yml@v1

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can take a look at &lt;a href="https://github.com/andyw8/setup-rails-example-app"&gt;this example app&lt;/a&gt; to see it in action.&lt;/p&gt;

&lt;p&gt;The first time you run the workflow may be slow, but subsequent runs should be much faster once the dependencies are cached.&lt;/p&gt;

&lt;p&gt;I’ve also shared a &lt;a href="https://railsbytes.com/templates/VMys8A"&gt;Railsbyte&lt;/a&gt; so that you can set this up with one command:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;rails app:template LOCATION="https://railsbytes.com/script/VMys8A"&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Principles and Configuration
&lt;/h2&gt;

&lt;p&gt;I designed &lt;code&gt;setup-rails&lt;/code&gt; so it should work at a basic level with no configuration needed for most apps.&lt;/p&gt;

&lt;p&gt;There are a few options you can enable depending on your app, such as RuboCop, Bundler Audit and RSpec - see the &lt;a href="https://github.com/andyw8/setup-rails"&gt;README&lt;/a&gt; for details.&lt;/p&gt;

&lt;p&gt;Currently only Postgres is supported but I’d like to expand to cover at least MySQL.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enhancements
&lt;/h2&gt;

&lt;p&gt;Building upon Matt’s great starting point, I made a few updates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Uses the latest versions of &lt;code&gt;setup-node&lt;/code&gt; and &lt;code&gt;setup-ruby&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Adds support for &lt;code&gt;rails test&lt;/code&gt; (e.g. Minitest) as well as RSpec&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/andyw8/setup-rails/blob/main/.github/dependabot.yml"&gt;Enabled Dependabot&lt;/a&gt; for updates of the actions that &lt;code&gt;setup-rails&lt;/code&gt; depends on&lt;/li&gt;
&lt;li&gt;Uses &lt;code&gt;setup-node&lt;/code&gt;’s JavaScript caching rather than a custom approach&lt;/li&gt;
&lt;li&gt;Skips &lt;code&gt;development&lt;/code&gt; gems when running Bundler to avoid unnecessary work.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Get Involved
&lt;/h2&gt;

&lt;p&gt;I’m already using this on most of my apps, but I’d love to get wider feedback.&lt;/p&gt;

&lt;p&gt;Please try &lt;a href="https://github.com/andyw8/setup-rails"&gt;setup-rails&lt;/a&gt; on your app and let me know how it works for you. Issues and pull requests are welcome!&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Avoiding common cron pitfalls when scaling Rails</title>
      <dc:creator>Andy Waite</dc:creator>
      <pubDate>Sun, 15 Mar 2020 00:00:00 +0000</pubDate>
      <link>https://dev.to/andyw8/avoiding-cron-pitfalls-when-scaling-rails-50f2</link>
      <guid>https://dev.to/andyw8/avoiding-cron-pitfalls-when-scaling-rails-50f2</guid>
      <description>&lt;p&gt;A common theme in business applications is the need for some kind of periodic task to run at a fixed interval, such as daily or weekly. This is often used for activities such as billing, pushing data to other systems, or integrating with a third-party API.&lt;/p&gt;

&lt;p&gt;The de facto tool to use for this is cron. It's somewhat archaic, but provides a reliable mechanism to declaratively define job schedules. The popular &lt;a href="https://github.com/javan/whenever"&gt;whenever&lt;/a&gt; gem provides an DSL to make this easy to use with Ruby.&lt;/p&gt;

&lt;p&gt;Simple tasks such as clearing a cache are a great match for cron.&lt;br&gt;
They run quickly and don't require significant system resources.&lt;/p&gt;

&lt;p&gt;The difficulty comes when cron is used to execute tasks which rely on the Rails application. It's easy to make use of cron for this, because we call the &lt;code&gt;rails runner&lt;/code&gt; command to invoke a method on a class.&lt;/p&gt;

&lt;p&gt;On a small app, this approach may be fine, but when scaling up there are some serious drawbacks.&lt;/p&gt;

&lt;p&gt;Whenever we use &lt;code&gt;rails runner&lt;/code&gt;, we're launching a completely separate instance of the application. Let's say your Rails app typically uses around 200MB of memory. To allow for some growth, we provision a server with 512MB of memory. If you schedule a cron job, the server's memory usage will temporarily spike to to 400MB.&lt;/p&gt;

&lt;p&gt;This might not even be noticed at first. Even if the machine runs short on memory, it can temporarily make use of swap space on disk.&lt;br&gt;
This might happen in the middle of night, when traffic is already low.&lt;/p&gt;

&lt;p&gt;But consider what happens once you have more scheduled jobs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;CalculateUsage&lt;/code&gt; runs daily at 2am&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GenerateReports&lt;/code&gt; runs weekly at 2am (every Monday)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;CreateInvoices&lt;/code&gt; runs monthly at 2am (first Monday of each month)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This means that once a month, all three jobs will be triggered at the same time. Your system needs to have enough capacity to run three instances, in addition to the main app.&lt;/p&gt;

&lt;p&gt;This could mean you need to over-provision your servers by 4x. That extra capacity will be idle most of the time. On a large site, you could be spending thousands of extra dollars per month. Or if you don't sufficiently provision, then you risk crashing your site.&lt;/p&gt;

&lt;p&gt;This risk is often exacerbated by the nature of schedule jobs.&lt;br&gt;
A web request typically lasts only for a few seconds at most.&lt;br&gt;
But a job may run for a much longer period, causing a large spike in memory use.&lt;/p&gt;
&lt;h2&gt;
  
  
  A First Approach
&lt;/h2&gt;

&lt;p&gt;Let's take a step back, and consider how we schedule jobs. Is it critical that each jobs runs at 2am? Probably not. The key thing probably that the job is complete by the beginning of the business day.&lt;/p&gt;

&lt;p&gt;A common first reaction to this problem is to try 'pad out' the jobs to prevent them overlapping. For example, instead of running each job at 2am, you run the first at 2.00am, the next at 2.05am, and the next at 2.10am.&lt;/p&gt;

&lt;p&gt;While this may provide a short term fix, it's not a sustainable solution. As your data grows, the time it takes to run each job will creep up, and will start to overlap again. You'll end up playing &lt;a href="https://en.wikipedia.org/wiki/Whac-A-Mole"&gt;Whac-A-Mole&lt;/a&gt; shifting jobs around.&lt;/p&gt;
&lt;h2&gt;
  
  
  A Better Approach
&lt;/h2&gt;

&lt;p&gt;As with many scaling problems, we can handle growth better if we can scale horizontally and add additional machines.&lt;/p&gt;

&lt;p&gt;We can achieve this with a distributed queue. In Rails, we typically use tools such as Sidekiq, Resque or Delayed Job.&lt;/p&gt;

&lt;p&gt;We could even configure this to auto-scale to handle varying workloads.&lt;/p&gt;

&lt;p&gt;Instead of using cron to execute the jobs, we'll use it to only enqueue them, e.g.:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;job&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;MonthlyReport&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;today&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="no"&gt;Delayed&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Job&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enqueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;queue: &lt;/span&gt;&lt;span class="s1"&gt;'cron'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This still involves booting up a separate Rails instance, but it's a fast operation. Spacing jobs one minute apart should be sufficient.&lt;/p&gt;

&lt;p&gt;You may later run into another problem of timeouts from jobs being too large. That can often be handled with a map-reduce approach, but that's beyond the scope of this post.&lt;/p&gt;

&lt;h2&gt;
  
  
  Further Improvement
&lt;/h2&gt;

&lt;p&gt;Instead of having to boot up an instance of the app just to enqueue a job, there's another approach we can take.&lt;/p&gt;

&lt;p&gt;We can add an internal API endpoint, such as &lt;code&gt;/api/internal/jobs&lt;/code&gt;. We can make an HTTP post, specifying the job to be enqueued. That means cron only needs to execute a call to something like curl, which uses vastly less resources. Obviously you should add some kind of authentication or restriction on the endpoint to avoid any possibility of a DOS attack.&lt;/p&gt;

</description>
      <category>rails</category>
      <category>cron</category>
      <category>scaling</category>
    </item>
    <item>
      <title>Speed up your TDD by refactoring rails_helper.rb</title>
      <dc:creator>Andy Waite</dc:creator>
      <pubDate>Sat, 14 Mar 2020 00:00:00 +0000</pubDate>
      <link>https://dev.to/andyw8/speed-up-your-tdd-by-refactoring-railshelper-rb-294f</link>
      <guid>https://dev.to/andyw8/speed-up-your-tdd-by-refactoring-railshelper-rb-294f</guid>
      <description>&lt;p&gt;Having a fast feedback cycle is critical element of successful test-driven development. You should be able to run a single test within one second.&lt;/p&gt;

&lt;p&gt;If you use rspec-rails, and your app has been around for more than a few years, the chances are that your &lt;code&gt;rails_helper.rb&lt;/code&gt; has accumulated a lot of cruft. This increases the time it takes to run a single test, probably to several seconds at least. As this usually happens gradually, it often goes unnoticed by those who work in a codebase every day.Developers may become accustomed to waiting ten seconds or more to run a single test.&lt;/p&gt;

&lt;p&gt;The default &lt;code&gt;rails_helper.rb&lt;/code&gt; generated by the rspec-rails installer is minimal and fast. There are a variety of things which tend to be added which slow it down:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Seeds tasks (such as from seed_fu).&lt;/li&gt;
&lt;li&gt;Requiring of all files within &lt;code&gt;spec/support&lt;/code&gt; (older versions of rspec-rails defaulted to this but this approach is now discouraged by the RSpec team).&lt;/li&gt;
&lt;li&gt;Global RSpec hooks such as &lt;code&gt;Before&lt;/code&gt; and &lt;code&gt;After&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Libraries to manage database state, such as DatabaseCleaner&lt;/li&gt;
&lt;li&gt;Setup for browser testing tools, such as Capybara&lt;/li&gt;
&lt;li&gt;Miscellaneous other testing support libraries.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Some of these will have a much bigger impact than others. You can use profiling tools to better understand the contribution of each.&lt;/p&gt;

&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;p&gt;The key to speeding up the TDD cycle is to only load what’s needed for a specific test.This strays from Rails’ convention of having everything auto-loaded, but I would argue it’s a worthwhile trade-off.&lt;/p&gt;

&lt;p&gt;(Note that this will probably not have any impact on your overall test suite time. The focus here is on the time for running an individual test or test file).&lt;/p&gt;

&lt;p&gt;Refactoring your whole test suite at once could take some time, perhaps several days for a large app.We want to do it gradually, in small steps, so that test suite stays green. Here’s how:&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementation
&lt;/h2&gt;

&lt;p&gt;First, we rename the existing &lt;code&gt;rails_helper.rb&lt;/code&gt; to something like &lt;code&gt;legacy_rails_helper.rb&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;We then update the existing references to that file with the new filename, which should be a simple global search and replace in your editor.&lt;/p&gt;

&lt;p&gt;Run your tests suite to ensure everything is still passing.&lt;/p&gt;

&lt;p&gt;Next, we create a ‘clean slate’ &lt;code&gt;rails_helper&lt;/code&gt;. An easy to do this is by re-running the rspec-rails generator, i.e. &lt;code&gt;rails generate rspec:install&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now, find a relatively basic test in your test suite. It’s usually easier to start with focused unit tests rather than integration tests, since they typically have more dependencies.&lt;/p&gt;

&lt;p&gt;Change the test to run with the new &lt;code&gt;rails_helper.rb&lt;/code&gt;. If it passes, then great, we’re done.&lt;/p&gt;

&lt;p&gt;Note that it’s important to verify that the tests passes individually. If you run the whole suite, a previously run test may have already loaded a necessary dependency. Since RSpec runs the tests in a random order, this may result in a false positive.&lt;/p&gt;

&lt;p&gt;But what if the test fails? Hopefully the test output will indicate that the failure is due to a missing dependency.&lt;/p&gt;

&lt;p&gt;There are a few courses of action you can take to resolve this.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You can determine what lines in &lt;code&gt;legacy_rails_helper.rb&lt;/code&gt; are needed, and copy to your &lt;code&gt;rails_helper.rb&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;You can use custom hooks so that particular lines are only executed for tests that are tagged with that hook name.&lt;/li&gt;
&lt;li&gt;You can copy only what’s required for that specific test.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s talk about the pros and cons of each.&lt;/p&gt;

&lt;p&gt;If we always copy the code back into &lt;code&gt;rails_helper.rb&lt;/code&gt; then we’ll end up close back where we started. So I try to reserve that for dependencies which are used in a large number of tests, e.g. something like FactoryBot.&lt;/p&gt;

&lt;p&gt;What about hooks? RSpec lets us run specific code for tests with a particular tag:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RSpec.configure do |config|
  config.before(:db) do
    require "some-library"
  end
end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This can be useful, but the downside is that we’re adding a layer of indirection. A developer reading this would need to know what a particular tag represents.&lt;/p&gt;

&lt;p&gt;The last approach is to be explicit about of each test’s dependencies, for example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;require "rails_helper"
require "some-library"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Communicating the Change
&lt;/h2&gt;

&lt;p&gt;If you’re working on a team, you’ll want to ensure that while the changeover is in progress, new tests are written using the new &lt;code&gt;rails_helper.rb&lt;/code&gt;. This is something you could also add to your project’s README or developer documentation so that others joining the team are also aware.You could also use a custom RuboCop check.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Steps
&lt;/h2&gt;

&lt;p&gt;Eventually, you’ll have moved every test over to the new &lt;code&gt;rails_helper&lt;/code&gt;. You can now delete &lt;code&gt;legacy_helper.rb&lt;/code&gt;. You may also discover there gems in your Gemfile which are no longer needed, and can be dropped. You may also be able to remove unused files from &lt;code&gt;spec/support&lt;/code&gt; if they are no longer referenced.&lt;/p&gt;

</description>
      <category>rails</category>
      <category>rspec</category>
      <category>tdd</category>
    </item>
    <item>
      <title>Migrating from Mocha to rspec-mocks</title>
      <dc:creator>Andy Waite</dc:creator>
      <pubDate>Sun, 15 Sep 2019 00:00:00 +0000</pubDate>
      <link>https://dev.to/andyw8/migrating-from-mocha-to-rspec-mocks-2khh</link>
      <guid>https://dev.to/andyw8/migrating-from-mocha-to-rspec-mocks-2khh</guid>
      <description>&lt;p&gt;At &lt;a href="https://www.financeit.io"&gt;Financeit&lt;/a&gt;, our main app uses RSpec for the test suite, but until recently it made use of &lt;a href="https://github.com/freerange/mocha"&gt;Mocha&lt;/a&gt; (not to be confused with the JavaScript library of the same name) for stubbing and mocking.&lt;/p&gt;

&lt;p&gt;We recently decided to switch to use rspec-mocks. Some reasons for this were:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We were already using rspec-mocks in other projects.&lt;/li&gt;
&lt;li&gt;There are more reference and training materials for rspec-mocks (books, screencasts, blog posts, etc).&lt;/li&gt;
&lt;li&gt;New developers joining the team were typically already familiar with rspec-mocks.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We didn’t have any problem with Mocha itself – it’s well-designed, has good documentation, and a responsive maintainer. The main reason was to have a simple and consistent stack.&lt;/p&gt;

&lt;h2&gt;
  
  
  Approach
&lt;/h2&gt;

&lt;p&gt;As our test suite is large, we knew that an incremental approach would be preferable.&lt;/p&gt;

&lt;p&gt;We made use of the &lt;a href="https://github.com/endeepak/rspec-multi-mock"&gt;rspec-multi-mock&lt;/a&gt; gem to allow Mocha and rspec-mocks to both be used at the same time.&lt;/p&gt;

&lt;p&gt;We then introduced a policy that new specs should be written using rspec-mocks rather than Mocha. Our code review process helped to remind developers of this.&lt;/p&gt;

&lt;p&gt;Next, we began the process of converting the test suite. Most Mocha has a direct mapping to rspec-mocks. I created this ‘cheatsheet’ for easy reference:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Mocha&lt;/th&gt;
&lt;th&gt;rspec-mocks&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;stub(a: 1)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;double(a: 1)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mock(a: 1)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;double(a: 1)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;foo.stubs(:a).returns(1)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;allow(foo).to receive(:a).and_return(1)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;foo.expects(:a).returns(1)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;expect(foo).to receive(:a).and_return(1)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Foo.any_instance.stubs(:a).returns(1)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;allow_any_instance_of(Foo).to receive(:a).and_return(1)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The most obvious way to make these conversions would be using regular expressions. And although this handles around 80% of conversions, the remainder would need to manually reviewed and edited, or would need very complex regular expressions.&lt;/p&gt;

&lt;p&gt;As a general rule when changing code with automatic tools, it’s better to modify the AST (Abstract Syntax Tree) than to manipulate strings.&lt;/p&gt;

&lt;p&gt;Ruby already has a powerful tools for this: &lt;a href="https://github.com/rubocop-hq/rubocop"&gt;RuboCop&lt;/a&gt;, which makes use of the&lt;a href="https://github.com/whitequark/parser"&gt;parser&lt;/a&gt; gem, has an auto-correct feature to change code to follow a preferred style.&lt;/p&gt;

&lt;h2&gt;
  
  
  RuboCop Custom Cops
&lt;/h2&gt;

&lt;p&gt;At &lt;a href="https://www.financeit.io"&gt;Financeit&lt;/a&gt;, we have a concept of ‘spike weeks’ where developers get to work on something they have a personal interest in. This gave me an opportunity to explore this area.&lt;/p&gt;

&lt;p&gt;RuboCop can be extended with &lt;a href="https://github.com/rubocop-hq/rubocop/blob/master/manual/extensions.md"&gt;custom cops&lt;/a&gt; – the documentation is somewhat limited, so it took some time to get the hang of, but after that I found it to be a very effective approach.&lt;/p&gt;

&lt;p&gt;I worked through the app in stages, beginning with &lt;code&gt;spec/controllers&lt;/code&gt;, then&lt;code&gt;spec/models&lt;/code&gt;, &lt;code&gt;spec/services&lt;/code&gt;, etc.&lt;/p&gt;

&lt;p&gt;The test suite itself helped to verify that the conversions were correct, since bad conversions should typically result in a syntax error, or a failing test.&lt;/p&gt;

&lt;p&gt;I ran into a few situations where the conversion didn’t work because of some strange approaches in the test, so took the opportunity to re-write the test.&lt;/p&gt;

&lt;p&gt;We’ve now converted around 95% of the test suite. We hope to soon reach 100%, when we can then remove the rspec-multi-mock and Mocha gems.&lt;/p&gt;

&lt;p&gt;I’ve published the RuboCop custom cops as the &lt;a href="https://github.com/andyw8/mocha_to_rspec"&gt;mocha_to_rspec&lt;/a&gt; gem.&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
    </item>
  </channel>
</rss>
