Stubbing Twitter search in Rails 5

Published Apr 23, 2018Last updated May 20, 2018

While working on a hobby project I ended up needing to stub out a search request to the Twitter API for testing purposes. I am using Rails 5, the twitter gem and Minitest.

There are some examples out there of mocking certain interactions of the Twitter client (this repo was very helpful), but I couldn’t find anything concerning search. I was trying to test a controller which only cared that it god a list of objects that responded to the text and uri methods, it didn’t know or care about the client or any interaction with Twitter.

Stubbing the Twitter client seemed like too much effort and not something that should belong in the controller test (this Reddit thread pointed me in the right direction). After some trial and error I settled on the following approach.

The controller’s only care is to serve some kind of array to the view:


class TweetsController < ActionController::Base 
  layout 'application' 
    def search 
    	@results = TwitterSearch.new.search(params[:q]) 
        render "tweets" 
    end 
end

The Twitter search class deals with the actual communication with Twitter and handling of exceptions:


class TwitterSearch 
  attr_reader :query 
  def client 
    	@client ||= Twitter::REST::Client.new do |config| 									config.consumer_key = ENV["CONSUMER_KEY"] 
    		config.consumer_secret = ENV["CONSUMER_SECRET"] 
        	config.access_token = ENV["ACCESS_TOKEN"] 
        	config.access_token_secret = ENV["ACCESS_SECRET"] 
        end 
     end 
     
     def search(query) 
     	begin 
        	results = client.search("#{query}", result_type: "recent").take(3).collect rescue results = [] 
        end 
     end 
end

For completeness, the view looks like this:


h1 Tweets .container = @results.each do |tweet| 
  p == tweet.text 
    a href=(tweet.uri) == tweet.uri
    

Finally, this is the test that I ended up with:


require 'test_helper' 
require 'minitest/mock' 

class TweetsControllerTest < ActionDispatch::IntegrationTest 
  test "should contain search parameter" do 
    	Tweet = Struct.new(:text, :uri) 
        test_tweet = Tweet.new("This is a test tweet", "example.com") 					twitter_search_stub = MiniTest::Mock.new twitter_search_stub.expect :search, [test_tweet], ["testquery"] 
        TwitterSearch.stub :new, twitter_search_stub do 
        	get "/tweets/search", params: {q: "testquery"} 
        	assert_response :success assert_select "p", "This is a test tweet" assert_select "a", "example.com" 
        end 
     end 
end

Let’s break that down a bit.


Tweet = Struct.new(:text, :uri) 
test_tweet = Tweet.new("This is a test tweet", "example.com")

The view expects an object with the text and uri attributes. There is no Tweet class in the system, so for the purposes of the test we create one using Struct.

twitter_search_stub = MiniTest::Mock.new 
twitter_search_stub.expect :search, [test_tweet], ["testquery"]


Create a mock that expects to respond to the search method, to return an array of tweets and to take an argument of “testquery”.


TwitterSearch.stub :new, twitter_search_stub do 
  get "/tweets/search", params: {q: "testquery"} 
  assert_response :success assert_select "p", "This is a test tweet" 
  assert_select "a", "example.com" 
end

Use the mock stub out the TwitterSearch class for everything that happeds withing the block.

There we go, mocked out Twitter search in Minitest.

View all posts by nimphal

Discover and read more posts from Nevelina Aleksandrova
get started