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

RFC for testing package design #1

Open
joshsmith opened this issue Nov 28, 2016 · 6 comments
Open

RFC for testing package design #1

joshsmith opened this issue Nov 28, 2016 · 6 comments

Comments

@joshsmith
Copy link
Contributor

We should discuss what this should look like.

Here are some notes from beam-community/stripity-stripe#124

The test package can distribute a lightweight replica of the Stripe service. That way, testing can be done something like the following:

test "retrieves all plans where the storage limit is greater than 30" do
  Stripe.Test.create(:plan, 10)
  Stripe.Test.create(:plan, 10, metadata: [storage_limit: 10])
  Stripe.Test.create(:plan, 10, metadata: [storage_limit: 35])
  Stripe.Test.create(:plan, 10, metadata: [storage_limit: 30])

  # This is business logic that will call the StripityStripe module
  plans = MyApp.Module.get_plans(:gt, 30) 

  assert Enum.count(plans) == 10
end

The data can be stored on a per-test basis in ETS, allowing users to build up the necessary state and tear it down without needing to know the intricacies of the system.

For example:

defmodule Stripe.CustomerTest do
  # this may be able to be true
  use ExUnit.Case, async: false
  
  # Most of this setup process can be abstracted into the library, but putting
  # it here so that others understand
  setup do
    # Starts a separate process
    #   - That process loads a Plug responder that binds to an available port
    #   - It also seeds an ETS table that it claims ownership of
    #   - The ETS table and Plug responder are bound to this specific test,
    #      so they are isolated to this test and any state manipulation on them
    #      will not affect other tests
    #   - Returns a struct of some form like %Stripe.Test.Server{port: 4515, pid: #PID<0.35.0>}
    {:ok, stripe_server} = Stripe.Test.Server.start()
    
    Application.put_env(:stripity_stripe, :api_base_url, "http://localhost:#{stripe_server.port}")
    
    on_exit fn ->
       #  Cleans up the ETS table and stops the plug responder and owning process
       Stripe.Test.Server.stop(stripe_server)
    end
    
    {:ok, stripe: stripe_server}
  end

  describe "Stripe.Customer.retrieve/1" do
    test "retrieves a customer by ID", %{stripe: stripe} do
      # Uses the Stripe.Test.Customer module to create a new customer in the
      # ETS backed server (essentially, this is a factory method)
      %{id: id} = Stripe.Test.Customer.create(stripe)

      # Makes an actual API request from the high level API through the low level
      # API and through the OS network stack
      {:ok, customer} = Stripe.Customer.retrieve(id)
      
      assert customer.id == id
    end
  end
end
@pdilyard
Copy link

How would this work if you were calling the Stripe modules from outside your actually test cases?

Would you need to put something like this in your code?:

if Mix.env == :test do
  Stripe.Test.Customer.create(args)
else
  Stripe.Customer.create(args)
end

Maybe this could be done more simply through some configuration option?

@pdilyard
Copy link

pdilyard commented Dec 12, 2016

Nevermind 😄 I like the approach used in code-corps-api:

# config.exs
config :my_app,
  stripe_api: Stripe

# config.test.exs
config :my_app,
  stripe_api: Stripe.Test

# my_service.ex
@stripe Application.get_env(:my_app, :stripe_api)
...
def charge do
  @stripe.Charges.create(...)
end

@johannesE
Copy link

Can we use a @behaviour-based approach for that? See also https://medium.com/@lasseebert/mocks-in-elixir-7204f8cc9d0f

@joshsmith
Copy link
Contributor Author

@johannesE thanks for the suggestion! Apologies if I'm being dense, but how exactly would @behaviour be used in this case?

@DavidAntaramian
Copy link

I thought I provided an updated spec from this one that used GenServer instead of ETS due to the ETS table limit.

In regards to @behaviour, that isn't useful here. The @behaviour methodology (which provides compile-time guarantees for the test double method) mocks the formal integration layer to reduce test surface area to the unit under test.

This testing library is for integration tests, not unit tests. Due to the way that the Stripe API is designed, integration tests can't be performed easily against the live service; furthermore, it's not possible for people to perform them without a Stripe API key and is difficult to perform during CI tests. So while you can't get a full integration test using it, it provides a near alternative.

@jayjun
Copy link

jayjun commented Jun 13, 2017

I believe Stripe's OpenAPI spec can be used to generate a realistic mock. See beam-community/stripity-stripe#227.

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

5 participants