Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I mock a class using Rspec and Rails?

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
like image 323
Dave Avatar asked Dec 01 '17 21:12

Dave


People also ask

What is mocking in RSpec?

Mocking is a technique in test-driven development (TDD) that involves using fake dependent objects or methods in order to write a test. There are a couple of reasons why you may decide to use mock objects: As a replacement for objects that don't exist yet.

What is the difference between mock and stub?

Stub: a dummy piece of code that lets the test run, but you don't care what happens to it. Substitutes for real working code. Mock: a dummy piece of code that you verify is called correctly as part of the test. Substitutes for real working code.

What RSpec method is used to create an example?

The it Keyword The word it is another RSpec keyword which is used to define an “Example”. An example is basically a test or a test case. Again, like describe and context, it accepts both class name and string arguments and should be used with a block argument, designated with do/end.

What is the difference between stubs and mocks in Ruby testing?

A Test Stub is a fake thing you stick in there to trick your program into working properly under test. A Mock Object is a fake thing you stick in there to spy on your program in the cases where you're not able to test something directly.


3 Answers

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

like image 116
Taryn East Avatar answered Nov 07 '22 07:11

Taryn East


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"
like image 42
JEMaddux Avatar answered Nov 07 '22 09:11

JEMaddux


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*)
like image 45
Bogdan Agafonov Avatar answered Nov 07 '22 08:11

Bogdan Agafonov