In a similar situation - mocking a class used internally by another class I'm trying to test - I found this to be a workable solution:
describe TilesAuth::Communicator do
class FakeTCPSocket
def initialize(*_); end
def puts(*_); end
end
context "when the response is SUCCESS" do
before do
class TilesAuth::Communicator::TCPSocket < FakeTCPSocket
def gets; 'SUCCESS'; end
end
end
after { TilesAuth::Communicator.send :remove_const, :TCPSocket }
it "returns success" do
communicator = TilesAuth::Communicator.new host: nil, port: nil, timeout: 0.2
response = communicator.call({})
expect(response["success"]).to eq(true)
expect(response).not_to have_key("error")
expect(response).not_to have_key("invalid_response")
end
end
end
I would have thought there would be a better way to do this - i.e. I couldn't see a way to pass in the desired return value for reuse - but this seems good enough for now. I'm new to mocking/factories, and I'd love to hear about any alternative methods.
Edit:
Ok, so not so similar after all.
I found a better way using RSpec mock, thanks to an excellent explanation in the RSpec Google Group:
context "with socket response mocked" do
let(:response) do
tcp_socket_object = instance_double("TCPSocket", puts: nil, gets: socket_response)
class_double("TCPSocket", new: tcp_socket_object).as_stubbed_const
communicator = TilesAuth::Communicator.new host: nil, port: nil, timeout: 0.2
communicator.call({})
end
context "as invalid JSON" do
let(:socket_response) { 'test invalid json' }
it "returns an error response including the invalid socket response" do
expect(response["success"]).to eq(false)
expect(response).to have_key("error")
expect(response["invalid_response"]).to eq(socket_response)
end
end
context "as SUCCESS" do
let(:socket_response) { 'SUCCESS' }
it "returns success" do
expect(response["success"]).to eq(true)
expect(response).not_to have_key("error")
expect(response).not_to have_key("invalid_response")
end
end
end