5

Guys bear with me on this. I would highly appreciate any advise provided.

So lets say I have a controller with the below code:

class PersonController < ApplicationController
  serialize_with personSerializer
  string: title
  int: age


  def update
    authorize(person, can_update?)
    person.update(title:title,age:age)

    serialize person
  end
end

So in terms of Business Logic we have here:

  1. check for authorize
  2. update the object
  3. return the serializer of the object (it's just a json result)

Now I want to test this piece of code with RSpec, but there is no DB to save or receive an object (well there was one earlier, but I want to remove it when running tests).

So far I have tried using nulldb gem to remove the db dependency. But the problem arrives when the created object cannot be retrieved.

If I want to test the authorize (1). It's a function of another gem that tests if the user can even use this controller.

Do I need to completely mock it? If so, how? Which tool to use?

Same for the update, if I do person.update, how should I check if it works if I don't have access to the active record?

3
  • 1
    You can still mock the update and force the response? 🤔 Commented Jun 12, 2022 at 6:27
  • @brcebn yes, but then I will skip the business logic... Commented Jun 12, 2022 at 6:29
  • 1
    You could test the model independently which makes this mock safe since you specify exactly the received parameters. Commented Jun 13, 2022 at 7:03

2 Answers 2

3
+25

Unless this is a system test, you probably shouldn't be be testing #authorize's functionality in your controller tests. Same goes for ActiveRecord#update. Unit tests should test the logic that exists in the unit of code that you are testing, not external dependencies. Therefore a perfectly acceptable solution would be to assert that those methods are called:

it "#update updates a person when can_update? is true" do
  # However you are creating person objects w/o a database.
  # Can also be any object.
  person = ""
  title = "Bob"
  age = 81
  can_update = true

  expect_any_instance_of(YourController).to receive(:authorize).with(person, true)
  expect(person).to receive(:update).with({ title: title, age: age })

  patch update, params: { title: title, age: age }

  expect(response).to have_http_status(:ok)
end

If you still want to test the business logic that lives in 3rd party libraries, you can define separate tests that isolate the desired functionality:

describe "authorize" do
  it "authorizes a person with permission" do
    person = Person.new(name: "Bob", age: 81)

    # Replace with what you expect to happen
    expect(authorize(person, true)).to be_truthy
  end
end

If you are committed to testing your external business logic in the controller, you'll need to manually mock out every method that the third party library calls. I would highly not recommend this approach as it is

  • brittle: if the library changes you may have to mock out more methods
  • verbose: you'll be writing a lot of code that does very little
  • non-specific: you'll be testing external business logic in test that should be focused on which code is rendered from a request
Sign up to request clarification or add additional context in comments.

Comments

0

eventually, I used nulldb, and just mock everything. also used FactroyBot. There is no easy way to do so, just hard work and mock all the stuff you need. mainly the DB calls.

For the authorize part i mocked the policy used:

allow_any_instance_of(MyPolicy).to receive(:model).and_return(true)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.