Do you want to know how you can use a test double or mock to test the creation of a record? Are you looking for an example of using mocks?… Maybe this post can help you =)
For better reference I have divided the task in 6 little steps:
Note: All the examples are using ruby and the rspec library, but if you want to understand the concept, I think you will be able to translate the examples to an other language without much trouble
As we are not going to use a “real database” we need something to use as the database… So we are going to expect to have an object that will represent the database… I like to use the word “store” to represent it because I try to express that is my “storage mechanism” but you can use the name that you want…
So if you are writing a Catalog application I would start with something like…
class DummyProductsStore
end
Now that you have an object, you need think in the function or method do you want to be called on that object to perform the record creation…
For example, if you are writing a Catalog application and you want to save Products… you can expect to do something like
store = DummyProductsStore
attrs = {
name: "My Product",
description: "The best product!"
}
store.create(attrs)
#or
store.insert(attrs)
#or
store.save(attrs)
or maybe you expect a behavior like…
store = DummyProductsStore
product = Product.new(
name: "My Product",
description: "The best product!"
)
store.create(product)
#or
store.insert(product)
#or
store.save(product)
Personally I prefer to use the first way….
attrs = { name: "My Product", description: "The best product!" }
store.create(attrs)
… I think that is easier if you expect to use something like the ActiveRecord library to talk with the database.
So… now that we have decided how we want our “store” to behave, let’s write the method in our DummyProductsStore
… but without nothing in it…
class DummyProductsStore
def self.create(attrs)
end
end
Normally when you want to use a test double to test the creation of a record, is because you have a higher level function that will trigger the expected behavior…
For example in our catalog application we want “create products” or “add products” so.. maybe we are exposing something like…
Catalog.add_product(attrs, store)
#or
Catalog::AddProduct.call(attrs, store)
Again… I prefer the first one but you can have your own taste…
Now that you have defined who is going to trigger the creation of a record, now you know where do you need to test it.
So inside the test file for that method (or object…) you can have one test like…
class DummyProductsStore
def self.create(attrs)
end
end
def store
DummyProductsStore
end
it "creates a record" do
attrs = { name: "P1", description: "Super Product" }
# This is the code that expects "store.create(attrs)" to be called
expect(store).to receive(:create).with(attrs)
Catalog.add_product(attrs, store)
end
And you can also write another test to for the cases that you don’t want the store to be called…
describe "without name" do
it "creates a record" do
attrs = { name: "", description: "Super Product" }
# This is the code that expects "store.create" not to be called
expect(store).not_to receive(:create)
Catalog.add_product(attrs, store)
end
end
In some cases you will compute something with the attributes that you are receiving… in that case you can add more specific cases with something like…
describe "creates a record adding a slug" do
example do
attrs = { name: "P1", description: "Super Product" }
expect(store).to receive(:create).with(attrs.merge(slug: "p1"))
Catalog.add_product(attrs, store)
end
example do
attrs = { name: "a FunKi Name", description: "Super Product" }
expect(store).to receive(:create).with(attrs.merge(slug: "a-funky-name"))
Catalog.add_product(attrs, store)
end
end
You have designed the expected behavior for the storage system… This means that you can use your function with a “real” storage system that behaves in the same way…
Note: Maybe for the “real” storage you will need or want to write some integration tests with the actual storage device.
For example, if you are using active record you can just use the active record class directly… because it behaves in the same way =)
class Product < ActiveRecord::Base
end
store = Product
Catalog.add_product(attrs, store)
… And I think that’s all for now… I hope it helps =)
I send an email each week, trying to share knowledge and fixes to common problems and struggles for ruby on rails developers, like How to fetch the latest-N-of-each record or How to test that an specific mail was sent or a Capybara cheatsheet. You can see more examples on Most recent posts or All post by topic.