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, 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. 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.