Rails: which test method to use - ruby-on-rails

I want to write some tests for my current project.
These tests will be mostly related to saved data in the database.
For example:
If a user registered 7 years ago, provide him gold badge (a boolean
field set to true)
If registered 3 years ago then bronze badge etc.
Well, the calculations are bit more complicated before the boolean field is set to true. There are about 30-40 test cases that I need to test which are very similar to the above two examples.
I did some reading about Rails testing but I could not figure out the exact difference between integration, unit and functional testing in Rails.
In my case which testing method will be appropriate?

I think it depends how you would implement it.
If you're using a cronjob to check for users badges an integration test would be good, to test the whole job enqueuing and stuff.
If you're updating when the user logs in, you can use a model (unit) test.
For example:
class UserTest < ActiveSupport::TestCase
test "users badge is updated on login if needed" do
user = User.create ... , golden_badge: false
travel_to Time.now + 7.years do
# simulate login, something like: user.login(user: "", pass: "")
assert user.golden_badge
end
end
end
this test would take place in test/models/user_test.rb

It depends where exactly the code that updates this field is executed. For example if you check these 'rewards' on the update of a user in user model itself as below:
class User < ActiveRecord::Base
before_save :check_rewards
def check_rewards
self.update(golden_badge: true) if created_at > 5.years.ago
end
Then the tests would only need to take place in the models/user_spec.rb which is a unit test.
If the code was ran as a cron job every hour, which used a method in a file called check_rewards.rb that would probably belong in two places.
Firstly the 'unit' test of the object that does the updating in services/check_rewards.rb (or commands/check_rewards.rb, or whatever directory) to test the actual functionality is working, like my user spec code above.
Finally you should have an integration test in integration/check_rewards_spec.rb to ensure that the job manager you are using is working as expected, so that the job itself is actually run and has the expected behavior.

Unit testing : For testing smallest unit of code. For a specific functionality. Like CRUD operation testing in a controller. Each action will test in unit testing separately.
Integration Testing : For testing a group of functionality or module. Consider if you want to see what a simple user can see and what logged in user can see in a website. So in both cases you need to test login functionality then a particular functionality after that.
Functional testing is completely different. In this you can test either module or combination of modules. It more like to integration testing.
Unit tests tell a developer that the code is doing things right; functional tests tell a developer that the code is doing the right things.
Read more here
In my view you should go for functional testing.

Related

Testing using time helpers

I am using the standard tools included in Rails 6 for testing. It's a very simple test, but it seems the freeze_time is not working, and the error code is quite difficult to discern a cause from.
Here is the test I am executing:
Here is the error after running the test:
When you create a new Person the value for created_at should be set (assuming it has timestamps applied), but since you're getting nil instead it is almost certain your Person creation fails. Likely due to validations errors when it tries to save. You could look at the model's error entries to be sure.
To get the error to show up for viewing:
class PersonTest < ActiveSupport::TestCase
test 'created at matches current time' do
freeze_time
assert_equal(Time.current, Person.create!.created_at)
end
end
If it is a validation error, you can bypass those:
class PersonTest < ActiveSupport::TestCase
test 'created at matches current time' do
freeze_time
person = Person.new.save(validate: false)
assert_equal(Time.current, person.created_at)
end
end
There are two things wrong with this though.
You want to avoid saving to the DB if at all possible during tests to keep them performant.
You are actually testing Rails's built-in functionality here. This is a test you should not be performing. There may be a better test to check that the Rails timestamps have been applied to your Person model, but that's not one I've ever written before (and I write tests for everything). There is no way to fat-finger the timestamps away in a commit, so testing for their existence feels way overkill.

How to test a Rails Model Class method which calls a method on all members?

In my Rails 4 project I've got a model, EventGenerator with an instance method generate (which creates some records in the database), and a class method generate_all which is intended to be called by a Rake task and looks something like this:
def self.generate_all
all.each(&:generate)
end
I can think of several approaches to testing this (I'm using RSpec 3 and Fabrication):
Don't bother - it's too simple to require tests. DHH says "Don't aim for 100% coverage". On the other hand, this is going to be called by a rake task, so won't be regularly exercised: I feel like that's a good reason to have tests.
Create a couple of EventGenerator instances in the database and use any_instance.should_receive(:generate) as the assertion - but RSpec 3 now recommends against this and requires a fudge to make it work. This is a personal 'showcase project' so if possible I'd like everything to be best-practice. Also (DHH aside) shouldn't it still be possible to create fast model specs which don't touch the database?
Like 2, but stub out EventGenerator.all to return some instances without touching the database. But stubbing the class under test is bad, right? And fragile.
Don't worry about unit testing it and instead cover it with an integration test: create a couple of generators in the database, run the method, check what gets changed/created in the database as a result.
Change the implementation: pass in an array of instances. But that's just pushing the problem back by a layer, and a test-driven change which I can't see benefitting the design.
Since I can't really think of a downside for option 4, perhaps that's the answer, but I feel like I need some science to back that up.
I would actually not bother to test it (so your 1.) as the method is really trivial.
If you would like to have it under test coverage though I'd suggest you to use your 3. My reasons are as follows:
Your test for .generate_all just needs to assert that the method #generate gets call on every instance returned by .all. It this context the actual implementation of .all is irrelevant and can be stubbed.
Your tests for #generate should assert that the method does the right thing. If these tests assert the proper functioning of this method, there's no need for the tests for .generate_all to duplicate any assertion.
Testing the proper functioning of #generate in the tests for .generate_all leads to unnecessary dependencies between the tests.

Is it worth testing low-level code such as scopes?

Is it actually profitable to test "core" features such as scopes and sorting? I have several tests that test functionality similar to what's below. It seems to me that it's just testing Rails core features (scopes and sorting), which are probably very well-tested already.
I know this may seem like an "opinion" question, but what I'm trying to find out is if someone knows anything I would miss if I assume that scopes/sorting are already tested and not valuable for developers to test.
My thoughts are that even if scopes/sorting are "broken", there's nothing I can really do if the Rails core code is broken as I refuse to touch core code without a very, very good reason (makes updating later on a nightmare...).
I feel "safe" with more tests but if these tests aren't actually providing value, then they are just taking up space and time on the test suite.
# user.rb model
class User < ActiveRecord::Base
scope :alphabetical, -> { order("UPPER(first_name), UPPER(last_name)") }
end
# user_spec.rb spec
context "scopes" do
describe ".alphabetical" do
it "sorts by first_name, last_name (A to Z)" do
user_one = create(:user, first_name: "Albert", "Johnson")
user_two = create(:user, first_name: "Bob", "Smith")
...
expect(User.all.alphabetical.first.first_name).to eq("Albert")
end
end
end
Yes, you should test the scope. It's not all built-in functionality; you had to write it. It sorts on two criteria, so it needs two tests, one to prove that it sorts by first name (which your test above shows), and another to prove that it falls back on last name when the first names are the same.
If the scope were simpler, you could skip unit-testing it if it was fully tested in an acceptance or higher-level unit test. But as soon as the scope itself needs more than one test, you should do that in tests of the scope itself, not in tests of higher-level code. If you tried to do it in tests of higher-level code it would be hard for readers to find where the details of the scope are tested, and it could lead to duplication if the tests of each caller tested all of the functionality of the scope.

Testing service classes - Rails

I was wondering how you guys would test a Service Object class in Rails? Let's say a User signs up. A user is created in the database, is added to the email list, and other stuff happens. How do you test this?
class UserRegistrar
def sign_up(user)
User.create(user) # or something to this effect
EmailMarketing.add_to_email_list(user)
SuperSecretClass.do_secret_stuff(user)
LoggingThing.new.log_stuff_about(user)
end
end
(Controller action)
def create
UserRegistrar.sign_up(params)
# stuff for the strong params, etc...
end
What I do is to just make sure that the methods are called, with the correct arguments. The results of the methods (like making sure that a user is really added to the list) are tested in their respective classes. Am I doing it right?
Yes, if I needed to write a unit test for a class like the one you show, I'd do it the way you say, with mocks. In your example, all of the work is delegated to high-level model methods which will need their own tests and might be used in more than one place, so it doesn't make sense to test the functionality of those methods in tests of the service. And there are not that many method calls to mock, so it won't be too painful.
However,
if any of those model methods were used only in one service, I'd consider moving those to the service to slim down the model and make the service more coherent. If I ended up with methods on the service that did a lot of work themselves, rather than just delegating, I'd test their functionality in tests of the service, by creating database objects and asserting how the service changes them.
on the other hand, if I had a service that only delegated, it might already be fully tested by my acceptance test (since I'm doing BDD and write acceptance tests first), and there would be no need to unit-test the service at all.
There's a danger to stubbing everything in your test, because then you're only testing that the class fits the test, instead of fitting in with the rest of your code. If EmailMarketing.add_to_email_list one day becomes EmailMarketing.add_to(:email_list ...) in a refactoring, your test wouldn't pick it up.
You can test the effects of the code like this, using User as an example:
expect {
UserRegistrar.sign_up(user)
}.to change{
user.persisted?
}.from(false).to(true)

Setting up a test in rspec with multiple "it" blocks

Say I have an instance method that does many different things that I need to test, something like store#process_order. I'd like to test that it sends an email to the customer, adds an entry in the orders table, charges a credit card, etc. What's the best way to set this up in rspec? Currently, I'm using rspec and factory girl I do something like this:
describe Store do
describe "#process_order" do
before do
#store = Factory(:store)
#order = Factory(:order)
# call the process method
#store.process_order(#order)
end
it 'sends customer an email' do
...
end
it 'inserts order to db' do
...
end
it 'charges credit card' do
...
end
end
end
But it feels really tedious. Is this really the right way to write a spec for a method that I need to make sure does several different things?
Note: I'm not interested in answers about whether or not this is good design. It's just an example I made up to help w/ my question - how to write these types of specs.
This is a good method because you can identify which element is broken if something breaks in the future. I am all for testing things individually. I tend not to check things get inserted into the database as you are then rails functionality. I simply check the validity of the object instead.
This is the method that is used in the RSpec book too. I would certainly recommend reading it if you are unsure about anything related to RSpec.
I think what you are doing is fine and I think it's the way rspec is intended to be used. Every statement (specification) about your app gets its own block.
You might consider using before (:all) do so that the order only has to get processed once but this can introduce dependencies on the order the specs are run.
You could combine all the code inside describe "#process_order" into one big it block if you wanted to, but then it would be less readable and rspec would give you less useful error messages when a spec fails. Go head and add raise to one of your tests and see what a nice error message you can get from rspec if you do it the way you are currently doing it.
If you want to test the entire process then we're talking about an integration test, not a unit test. If you want to test #process_order method which does several things, then I'd expect those things mean calling other methods. So, I would add #should_receive expectations and make sure that all paths are covered. Then I would spec all those methods separately so I have a nice unit spec suite for everything. In the end I would definitely write an integration/acceptance spec which checks if all those pieces are working together.
Also, I would use #let to setup test objects which removes dependencies between spec examples (it blocks). Otherwise a failure of one of the examples may cause a failure in other example giving you an incorrect feedback.

Resources