Reputation: 414
There are two classes, UserDevice(< ActiveRecord::Base) and NotificationAdapter.
In NotificationAdapter, I use AWS SDK, and UserDevice uses NotificationAdapter instance.
The Joint point is below,
protected def notificationAdapter
@notificationAdapter ||= NotificationAdapter.new(self)
end
In UserDevice test, I want to make temporary NotificationAdapter mock for replacing the original NotificationAdapter, and use this mock only in the test.
But I don't know how to do, because this is my first case using mock in test.
I think it requires two steps below,
Make temporary NotificationAdapter class(NorificationAdapterMock) in test code.
NotificationAdapterMock = MiniTest::Mock.new
mock.expect :setEndpoint, 'arn:testEndpoint'
mock.expect :subscribeToAnnouncement, true
Change notificationAdapter method of UserDevice to below,
protected def notificationAdapter
@notificationAdapter ||= NotificationAdapterMock
end
But I don't know whether it is right or wrong. What should I do?
Upvotes: 3
Views: 3448
Reputation: 7482
You need to
mock
your NotificationAdapter
, so it doesn't fire the network but instead does something safe and easySide note: Please, follow ruby style guidelines, method names and variables should be written with snake case and not camel case.
So, we may write it this way:
require 'minitest/autorun'
class NotificationAdapter
def initialize(device)
@device = device
end
def notify
# some scary implementation, that we don't want to use in test
raise 'Boo boo!'
end
end
class UserDevice
def notification_adapter
@notification_adapter ||= NotificationAdapter.new(self)
end
def notify
notification_adapter.notify
end
end
describe UserDevice do
it 'should use NotificationAdapter for notifications' do
device = UserDevice.new
# create mock adapter, that says 'ohai!' on notify
mock_adapter = MiniTest::Mock.new
mock_adapter.expect :notify, 'ohai!'
# connect our mock, so next NoficationAdapter.new call will return our mock
# and not usual implementation
NotificationAdapter.stub :new, mock_adapter do
device.notify.must_equal 'ohai!'
end
end
end
More information can be found in MiniTest's documentation on mocks and stubs.
But let's don't stop here! I advise you to move your business logic out from your ActiveRecord
models to a separate service class. This will have following good effects:
Here it is:
require 'minitest/autorun'
# same adapter as above
class NotificationAdapter
def initialize(device)
@device = device
end
def notify
raise 'Boo boo!'
end
end
class UserDevice
# the logic has been moved out
end
class NotifiesUser
def self.notify(device)
adapter = NotificationAdapter.new(device)
adapter.notify
end
end
describe NotifiesUser do
it 'should use NotificationAdapter for notifications' do
# Let's mock our device since we don't need it in our test
device = MiniTest::Mock.new
# create mock adapter, that says 'ohai!' on notify
mock_adapter = MiniTest::Mock.new
mock_adapter.expect :notify, 'ohai!'
# connect our mock, so next NoficationAdapter.new call will return our mock
# and not usual implementation
NotificationAdapter.stub :new, mock_adapter do
NotifiesUser.notify(device).must_equal 'ohai!'
end
end
end
Have a nice day!
P.S. If you want to know more about testing in isolation, mocking techniques and general "fast rails tests" movement I highly recommend you Gary Bernhardt's destroyallsoftware.com screencasts. This is paid stuff, but it very worths it.
Upvotes: 8