Monday, December 11, 2017

How do I mock a class using Rspec and Rails?

Leave a Comment

I'm using Rails 5 with Rspec 3. How do I mock a class in my Rspec method? I have the following class

require 'rails_helper'  describe CryptoCurrencyService do    describe ".sell" do      it "basic_sell" do       last_buy_price = 3000       last_transaction = MoneyMakerTransaction.new({         :transaction_type => "buy",         :amount_in_usd => "100",         :btc_price_in_usd => "#{last_buy_price}"       })       @client = Coinbase::Wallet::Client.new(api_key: ENV['COINBASE_KEY'], api_secret: ENV['COINBASE_SECRET'])       sell_price = 4000       assert sell_price > last_buy_price * (1 + MoneyMakerThreshhold.find_buy.pct_change)        allow(@client).to receive(:sell_price).and_return({"base"=>"BTC", "currency"=>"USD", "amount"=>"#{sell_price}"})        svc = CryptoCurrencyService.new       svc.sell(last_transaction)       last_transaction = MoneyMakerTransaction.find_latest_record       assert last_transaction.transaction_type, "sell"     end    end  end 

Instead of actually instantiating the class "Coinbase::Wallet" in the line

@client = Coinbase::Wallet::Client.new(api_key: ENV['COINBASE_KEY'], api_secret: ENV['COINBASE_SECRET']) 

I'd like to create mock taht I could then insert into my service class, which I'm testing. As it stands right now, when I run things, the actual underlying class is getting instantiated, resulting the run time error ...

  1) CryptoCurrencyService.sell basic_sell      Failure/Error: payment_method = client.payment_methods()[0]       Coinbase::Wallet::AuthenticationError:        invalid api key 

3 Answers

Answers 1

rspec mocks and stubs can be used on any class. For example:

coinbase_mock = double(api_key: ENV['COINBASE_KEY'], api_secret: ENV['COINBASE_SECRET']) expect(Coinbase::Wallet::Client).to_receive(:new).and_return(coinbase_mock) 

then you can add whatever you like to the coinbase_mock so that it quacks like the class you need... :)

Answers 2

You could use a Ruby Struct like this:

Coinbase::Wallet::Client = Struct.new(:api_key, :api_secret) @client = Coinbase::Wallet::Client.new(ENV['COINBASE_KEY'], ENV['COINBASE_SECRET']) @client.api_key #=> whatever was in ENV['COINBASE_KEY'] 

Then pass that object in.

If you need behavior on it you can also get that like this:

Coinbase::Wallet::Client = Struct.new(:api_key, :api_secret) do   def client_info     ## logic here     "info"   end end  @client = Coinbase::Wallet::Client.new(ENV['COINBASE_KEY'], ENV['COINBASE_SECRET']) @client.client_info #=> "info" 

Answers 3

Preferred RSpec (since ver. 3) style would be

let(:coinbase_client) { instance_double(Coinbase::Wallet::Client) }  # validates that mocked/stubbed methods present in class definitiion  before do   allow(coinbase_client).to receive(:sell_price).and_return({"base"=>"BTC", "currency"=>"USD", "amount"=>"PRICE YOU PROVIDE"}) end 

docs about instance_double method

while you inject coinbase_client as a construction parameter to your classes that use it internally

OR if for some reasons you can't use dependancy injection, you could mock any instance of Coinbase::Wallet::Client with

allow_any_instance_of(Coinbase::Wallet::Client).to receive(... *mock specific method*) 
If You Enjoyed This, Take 5 Seconds To Share It

0 comments:

Post a Comment