Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Testing controllers that inherit from ShopifyApp::AuthenticatedController #445

Closed
ahogerland opened this issue Jul 20, 2017 · 9 comments
Closed

Comments

@ahogerland
Copy link

This was initially posted on the Shopify forums, but was recommended to post it here.

I am using Minitest for testing. My controllers inherit from ShopifyApp::AuthenticatedController, and so in order to test them I need to somehow stub/mock a Shopify session. Previously with ActionController::TestCase there was a simple solution to this:

@request.session[:shopify] = shop.id
@request.session[:shopify_domain] = shop.shopify_domain

But with the move to ActionDispatch::IntegrationTest, there is no longer any access to the @request instance variable when testing controllers.

What are the best practices for testing controllers that inherit from ShopifyApp::AuthenticatedController in Rails 5?

@kevinhughes27
Copy link
Contributor

Great question. In Rails 5 Integration Tests are just as fast as controller tests but cover more area so controller tests were removed. This does mean that some kinds of mocking like you described don't work anymore. The solution is fairly intuitive but easy to miss if you're used to controller tests.

Every test starts by calling a helper which follows the authentication flow which internally sets the session for you. Then make the desired second request to test in your test.

def login_to_shopify
  get '/auth/shopify'
  follow_redirect!
  # session is available now. You could look up the Shop model here and return it if you want
end

Of course this isn't enough since this request gets directed to a 3rd party. This is where Omniauth test mode comes in:

OmniAuth.config.test_mode = true
OmniAuth.config.mock_auth[:shopify] = # shop object

@Ninigi
Copy link

Ninigi commented Sep 28, 2017

@kevinhughes27 could you elaborate on this line?

OmniAuth.config.mock_auth[:shopify] = # shop object

I am guessing this has to be an OmniAuth::AuthHash as per this omniauth wiki, but I am having a hard time filling in the blanks here.
Thanks in advance 🙇

EDIT:
I figured something out

def login(shop)
  OmniAuth.config.test_mode = true
  OmniAuth.config.add_mock(:shopify,
    uid: shop.shopify_domain,
    credentials: { token: shop.shopify_token })
  # And because I am using RSpec (don't know if Minitest needs this):
  Rails.application.env_config["omniauth.auth"] = OmniAuth.config.mock_auth[:shopify]
  
  get "/auth/shopify"
end

# Then in my test
shop = FactoryGirl.create(:shop) # or whatever way you like to create test data
login(shop)

Still not sure if this is the right way to do it though, so any feedback would be welcome 👍

@kevinhughes27
Copy link
Contributor

That looks correct to me!

Also I've always wanted to include some test helpers in this gem if you want to PR it :)

@Ninigi
Copy link

Ninigi commented Sep 29, 2017

@kevinhughes27 the biggest problem is, that you need to trigger the authorization differently depending on the usecase... For example in a capybara test, you would need to do visit "/auth/shopify". Not even talking about other frameworks here :(
Not impossible to solve, but I just don't have the time right now.

@kevinhughes27
Copy link
Contributor

Fair enough! I've run into that as well and I'm not sure the cleanest way to solve it either.

@waseem
Copy link

waseem commented Nov 10, 2017

@Ninigi How did you get that working? I face an error when trying to do above.

So I have

# spec/controllers/admin/dashboards_controller_spec.rb

require 'rails_helper'
RSpec.describe Admin::DashboardsController, type: :controller do
  fixtures :shops

  describe "#show" do
    let!(:shop) { shops(:a_shop) }
    before do
      login(shop)
    end

    it "passes" do
      expect(true).to be
    end
  end
end
# spec/support/admin_controller_test_helpers.rb

module AdminControllerTestHelpers
  def login(shop)
    OmniAuth.config.test_mode = true
    OmniAuth.config.add_mock(:shopify, uid: shop.shopify_domain, credentials: { token: shop.shopify_token })
    Rails.application.env_config["omniauth.auth"] = OmniAuth.config.mock_auth[:shopify]

    get "/auth/shopify"
  end
end

And the error is:

1) Admin::DashboardsController#show passes
     Failure/Error: get "/auth/shopify"
     
     ActionController::UrlGenerationError:
       No route matches {:action=>"/auth/shopify", :controller=>"admin/dashboards"}

This has something to do with how rspec works. It assumes /auth/shopify to be an action in the DashboardsController rather than querying out the URL as it is.

@Ninigi
Copy link

Ninigi commented Nov 10, 2017

@waseem It is 1:30 AM for me right now and I really do not recall this issue very well, so please forgive me for giving spongy advice... It looks like you are trying to execute the login at a time, where not all the necessary data is available, as in the method I described heavily relies on an rspec controller/request context.
I don't know what the problem is, but my gut says require the helper file in your TEST file instead of the rspec_helper.
If that is not the problem, the error message indicates that something is wrong with how your shopify gem is set up, because it should provide the auth/shopify route.
If that does not help, please open another issue with a reference to this one and a detailed description of your configuration files. Maybe the shopify guys can help you out 🚒

waseem added a commit to waseem/owl that referenced this issue Jan 7, 2018
@m20io
Copy link

m20io commented Jan 29, 2018

I ran into same issues, with not matching route. I suspect rspec only setting up the controller under test routes. Has anyone a working example that could be posted?

@richardmonette
Copy link

require 'test_helper'

class AssetsControllerTest < ActionDispatch::IntegrationTest
  def login(shop)
    OmniAuth.config.test_mode = true
    OmniAuth.config.add_mock(
      :shopify,
      provider: 'shopify',
      uid: shop.shopify_domain,
      credentials: { token: shop.shopify_token }
    )

    get "/auth/shopify"
    follow_redirect!

    ShopifyAPI::Shop.stubs(:current).returns(shop)
  end

  setup do
    login(shops(:regular_shop))
  end

  test "associate should return the corresponding asset if it exists" do
    assert_no_difference('Asset.count') do
      get assets_associate_url
    end

    assert_response :ok
  end
end

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants