How do I mock a class using Rspec and Rails?
Asked Answered
N

3

7

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
Nureyev answered 1/12, 2017 at 21:47 Comment(2)
Have you ha da look at rspec-mocks? github.com/rspec/rspec-mocksParasitic
Yeah but I don't see anyting that quite fits my situation.Nureyev
P
8

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... :)

Parasitic answered 4/12, 2017 at 22:53 Comment(0)
B
3

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"
Boomer answered 5/12, 2017 at 19:59 Comment(0)
L
1

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*)
Laryngeal answered 11/12, 2017 at 14:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.