【问题标题】:Testing Elixir/Phoenix Service modules测试 Elixir/Phoenix 服务模块
【发布时间】:2018-05-30 11:45:11
【问题描述】:

我一直在玩 Elixir/Phoenix 第三方模块。 (用于从 3rd 方服务获取一些数据的模块)其中一个模块看起来像这样:

module TwitterService do
  @twitter_url "https://api.twitter.com/1.1"

  def fetch_tweets(user) do
     # The actual code to fetch tweets
     HTTPoison.get(@twitter_url)
     |> process_response
  end      

  def process_response({:ok, resp}) do
    {:ok, Poison.decode! resp}
  end

  def process_response(_fail), do: {:ok, []}
end

在我的问题中,实际数据并不重要。所以现在,我对如何在测试中动态配置@twitter_url 模块变量以使某些测试故意失败很感兴趣。例如:

module TwitterServiceTest
  test "Module returns {:ok, []} when Twitter API isn't available"
    # I'd like this to be possible ( coming from the world of Rails )
    TwitterService.configure(:twitter_url, "new_value") # This line isn't possible
    # Now the TwiterService shouldn't get anything from the url
    tweets = TwitterService.fetch_tweets("test")
    assert {:ok, []} = tweets
  end
end

我怎样才能做到这一点? 注意: 我知道我可以在devtest 环境中分别使用:configs 来配置@twiter_url,但我希望能够根据来自Twitter 的真实响应进行测试API 也是如此,这会改变整个测试环境的 URL。
我想出的解决方案之一是

def fetch_tweets(user, opts \\ []) do
  _fetch_tweets(user, opts[:fail_on_test] || false)
end

defp _fetch_tweets(user, [fail_on_test: true]) do
  # Fails
end

defp _fetch_tweets(user, [fail_on_test: false]) do
  # Normal fetching
end

但这看起来很老套和愚蠢,必须有更好的解决方案。

【问题讨论】:

    标签: testing elixir phoenix-framework httpoison


    【解决方案1】:

    正如 José 在 Mocks And Explicit Contracts 中所建议的,最好的方法可能是使用依赖注入:

    module TwitterService do
      @twitter_url "https://api.twitter.com/1.1"
    
      def fetch_tweets(user, service_url \\ @twitter_url) do
         # The actual code to fetch tweets
         service_url
         |> HTTPoison.get()
         |> process_response
      end      
    
      ...
    end
    

    现在在测试中,您只需在必要时注入另一个依赖项:

    # to test against real service
    fetch_tweets(user)
    
    # to test against mocked service
    fetch_tweets(user, SOME_MOCK_URL)
    

    这种方法还将使将来插入不同的服务变得更加容易。处理器的实现不应该依赖于它的底层服务,假设服务遵循一些契约(在这种特殊情况下使用 json 给定一个 url。)

    【讨论】:

      【解决方案2】:

      config 在这里听起来是个好方法。您可以在测试中在运行时修改配置中的值,然后在测试后恢复它。

      首先,在您的实际代码中,使用@twitter_url,而不是Application.get_env(:my_app, :twitter_url)

      然后,在您的测试中,您可以像这样使用包装函数:

      def with_twitter_url(new_twitter_url, func) do
        old_twitter_url = Application.get_env(:my_app, :twitter_url)
        Application.set_env(:my_app, :twitter_url, new_twitter_url)
        func.()
        Application.set_env(:my_app, :twitter_url, old_twitter_url)
      end
      

      现在在你的测试中,做:

      with_twitter_url "<new url>", fn ->
        # All calls to your module here will use the new url.
      end
      

      确保您没有为此使用异步测试,因为此技术会修改全局环境。

      【讨论】:

      • 这似乎是一种处理此问题的 javascript-y 方式。我喜欢这背后的想法,但唯一担心的是它会使使用它的测试不是异步的。每当有人忘记在测试中放置 async: true 时,这可能会导致错误(如果多个开发人员在一个项目上工作)
      • async 默认为 false,因此您必须显式打开它才能使其中断。但是,是的,如果@mudasobwa 的解决方案对您的用例实用(没有太多端点),我会选择它。
      • 如果您不想修改模块实现中的任何代码,则此方法有效,因此我将应用它!
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-02-25
      • 1970-01-01
      • 2019-01-08
      • 1970-01-01
      相关资源
      最近更新 更多