2017-12-16 7 views
0

Ich habe mit Elixir/Phoenix Modulen von Drittanbietern gespielt. (Module, die verwendet werden, einige Daten aus einer 3rd-Party-Service zu holen) Einer dieser Modul, die wie so:Elixir/Phoenix Service Module testen

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 

Die aktuellen Daten keine Rolle spielt in meiner Frage. Jetzt interessiert mich, wie ich die Modulvariable in Tests dynamisch konfigurieren kann, damit einige der Tests absichtlich fehlschlagen. Zum Beispiel:

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 

Wie kann ich das erreichen? Ich weiß, ich :configs kann @twiter_url separat in dev und test Umgebungen zu konfigurieren, aber ich möchte in der Lage sein, auf einer echte Antwort von dem Twitter-API zu testen, und das würde die URL auf dem gesamten ändern: Hinweis Test Umgebung.
Eine der Lösungen, die ich kam mit war

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 

Aber das scheint nur hackish und albern, muss es eine bessere Lösung für dieses Problem sein.

Antwort

2

Wie es von José in Mocks And Explicit Contracts vorgeschlagen wurde, wäre der beste Weg, wahrscheinlich ein Dependency Injection zu verwenden:

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 

Jetzt in Tests, die Sie gerade eine andere Abhängigkeit injizieren, wenn notwendig:

# to test against real service 
fetch_tweets(user) 

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

Dieser Ansatz wird es auch erleichtern, in Zukunft verschiedene Dienste anzuschließen. Die Prozessorimplementierung sollte nicht von ihrem zugrunde liegenden Dienst abhängen, vorausgesetzt, der Dienst folgt einem Vertrag (antwortet mit json in einem bestimmten Fall mit einer URL).

1

config klingt wie ein guter Weg hier. Sie können den Wert in der Konfiguration zur Laufzeit in Ihrem Test ändern und ihn dann nach dem Test wiederherstellen.

Zuerst in Ihrem tatsächlichen Code, anstatt @twitter_url, verwenden Sie Application.get_env(:my_app, :twitter_url).

Dann in Ihren Tests können Sie eine Wrapper-Funktion wie folgt verwenden:

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 

Jetzt in Ihrem Tests tun:

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

Stellen Sie sicher, Sie sind nicht async Tests mit für Dies, da diese Technik die globale Umgebung verändert.

+0

Dies scheint wie eine Javascript-y-Methode, dies zu behandeln. Ich mag die Idee dahinter, aber die einzige Sorge ist, dass Tests, die dies verwenden, nicht asynchron sind. Dies könnte einen Fehler verursachen, wenn jemand vergisst, Async zu setzen: true bei einem Test (wenn mehrere Entwickler an einem Projekt arbeiten) –

+0

'async' ist standardmäßig falsch, also müssten Sie es explizit aktivieren, damit es bricht. Aber ja, ich würde mit @ mudasobwas Lösung gehen, wenn es für Ihren Anwendungsfall praktisch ist (nicht zu viele Endpunkte). – Dogbert

+1

Dies funktioniert, wenn Sie keinen Code in der Modulimplementierung ändern möchten, also würde ich dies anwenden! –

Verwandte Themen