Test poprawności access tokena z API przy użyciu WireMock

Test poprawności access tokena z API przy użyciu WireMock
Ornstein
  • Rejestracja:około 2 lata
  • Ostatnio:około 14 godzin
  • Postów:111
1

Cześć,
mam funkcjonalność, w której aplikacja komunikuje się z API Spotify w celu uzyskania access tokena dla użytkownika.

Chciałbym napisać test do tej funkcjonalności, korzystając z WireMocka. Mockuję odpowiedź API, zwracając JSON zapisany w pliku.

W teście chciałbym sprawdzić, czy metoda faktycznie zwraca poprawny access token.

Pomyślałem, że pobiorę access token z odpowiedzi zwróconej przez metodę i porównam z access tokenem ręcznie pobranym z pliku JSON. Czy takie podejście jest dobre?

Kopiuj
@Test
void whenRetrieveToken_ReturnExpectedToken() throws IOException {
    mockApiResponse(200, response);

    Result <String> result = accessTokenHandler.retrieveSpotifyAccessToken("auth_code", generateJwtTokenForUser());

    assertThat(result.data()).isEqualTo(expectedToken());
}

private void mockApiResponse(int statusCode, String response) {
    wireMockServer.stubFor(post("/v1/spotify/auth/api/token")
            .willReturn(aResponse()
                    .withStatus(statusCode)
                    .withHeader("Content-Type", "application/json")
                    .withBody(response)));
}

private String expectedToken() throws IOException {
    String expectedToken;
    try (InputStream inputStream = getClass().getClassLoader().getResourceAsStream("spotify_access_token_response.json")) {
        expectedToken = jsonFieldExtractor.getFieldValueFromJson(inputStream, "access_token", String.class);
    }
    return expectedToken;
}
Kopiuj
public class JsonFieldExtractor {
    private final ObjectMapper objectMapper;
    public JsonFieldExtractor(ObjectMapper objectMapper) {
        this.objectMapper = objectMapper;
    }

    public <T> T getFieldValueFromJson(InputStream inputStream, String fieldPath, Class<T> valueType) throws IOException {
        JsonNode jsonNode = objectMapper.readTree(inputStream);

        JsonNode fieldValue = jsonNode.at("/" + fieldPath.replace(".", "/"));

        if (fieldValue.isMissingNode()) {
            throw new IllegalArgumentException(String.format("Field '%s' not found in JSON input", fieldPath));
        }

        return objectMapper.treeToValue(fieldValue, valueType);
    }
}
edytowany 1x, ostatnio: Riddle
PI
  • Rejestracja:ponad 9 lat
  • Ostatnio:4 miesiące
  • Postów:2787
3

Według mnie ten test testuje jedynie to, czy Twoja metoda accessTokenHandler.retrieveSpotifyAccessToken woła URL /v1/spotify/auth/api/token.

MA
  • Rejestracja:ponad rok
  • Ostatnio:minuta
  • Postów:50
1

Myślę, że w porządku, też tak robię testy moich funkcjonalności.

Riddle
Administrator
  • Rejestracja:prawie 15 lat
  • Ostatnio:mniej niż minuta
  • Lokalizacja:Laska, z Polski
  • Postów:10085
1

Moim zdaniem to się średnio opłaca. Za bardzo test przywiązany jest do struktury API spotify, nie jest Ci to na rękę. Wygląda to trochę tak, jakbyś chciał swoimi testami sprawdzić, czy Spotify przyznał Ci dostęp, a tak się nie da.

Do czego chcesz użyć tego access tokenu który wyciągasz?

edytowany 1x, ostatnio: Riddle
KE
  • Rejestracja:ponad 6 lat
  • Ostatnio:około 6 godzin
  • Postów:681
2

Ten kodzik z pewnością przejdzie typowe korporeview w stylu "hmm, a napisał testy? A, coś jest, jest adnotacja @Test, no to approve, następny.". 😉

Co samo w sobie ma jakąś wartość (zaliczone review), ale w praktyce niewiele daje. Nie dałeś całego kodu, ale jeśli dobrze rozumiem:

  1. instruujesz mocka, żeby pod urlem /x zwrócił dany response y
  2. odpytujesz mocka o url /x
  3. sprawdzasz czy zwróciło się y

Czy to ma sens?

Ornstein
  • Rejestracja:około 2 lata
  • Ostatnio:około 14 godzin
  • Postów:111
0
Riddle napisał(a):

Moim zdaniem to się średnio opłaca. Za bardzo test przywiązany jest do struktury API spotify, nie jest Ci to na rękę. Wygląda to trochę tak, jakbyś chciał swoimi testami sprawdzić, czy Spotify przyznał Ci dostęp, a tak się nie da.

Do czego chcesz użyć tego access tokenu który wyciągasz?

Potrzebuję tego tokenu do utworzenia playlisty. Token zawiera uprawnienia.

Riddle
Administrator
  • Rejestracja:prawie 15 lat
  • Ostatnio:mniej niż minuta
  • Lokalizacja:Laska, z Polski
  • Postów:10085
0
Ornstein napisał(a):

Do czego chcesz użyć tego access tokenu który wyciągasz?

Potrzebuję tego tokenu do utworzenia playlisty. Token zawiera uprawnienia.

No widzisz, więc to jest Twoja faktyczna funkcjonalność którą powinieneś testować. "Wyciąganie tokenu" to jest tylko implementacja tego.

@Ornstein W jaki sposób Twoja aplikacja będzie tworzyć tą playlistę? Użytkownik będzie wybierał piosenki, czy jak?

edytowany 1x, ostatnio: Riddle
Ornstein
  • Rejestracja:około 2 lata
  • Ostatnio:około 14 godzin
  • Postów:111
0
Riddle napisał(a):
Ornstein napisał(a):

Do czego chcesz użyć tego access tokenu który wyciągasz?

Potrzebuję tego tokenu do utworzenia playlisty. Token zawiera uprawnienia.

No widzisz, więc to jest Twoja faktyczna funkcjonalność którą powinieneś testować. "Wyciąganie tokenu" to jest tylko implementacja tego.

Ok. Na ten moment podzieliłem to w taki sposób, że osobno obsługuję proces logowania do Spotify. Jeśli zakończy się on sukcesem, zapisuję token w Redis, a jako klucz wykorzystuję ID użytkownika pobrane z JWT. Osobno będzie tworzenie playlisty.

Riddle napisał(a):

@Ornstein W jaki sposób Twoja aplikacja będzie tworzyć tą playlistę? Użytkownik będzie wybierał piosenki, czy jak?

Na podstawie pogody w podanej przez użytkownika lokalizacji.

Riddle
Administrator
  • Rejestracja:prawie 15 lat
  • Ostatnio:mniej niż minuta
  • Lokalizacja:Laska, z Polski
  • Postów:10085
1
Riddle napisał(a):

@Ornstein W jaki sposób Twoja aplikacja będzie tworzyć tą playlistę? Użytkownik będzie wybierał piosenki, czy jak?

Na podstawie pogody w podanej przez użytkownika lokalizacji.

No okej, to się wydaje główną logiką programu. W tym miejscu powinieneś testować głównie.

Widzę że działasz systemem:

  1. chcesz funkcję
  2. myślisz jak ją zaprogramować
  3. znajdujesz sposób (np. ten access token)
  4. i myślisz jak teraz przetestować ten access token.

To jest słabe podejście. Lepiej byłoby na to popatrzeć tak:

  1. Chcesz funkcję, np. żeby program utworzył playlistę na podstawie pogody.
  2. I pod to piszesz test! Nie pod Twoją implementację którą sobie wymyśliłeś.
  3. Na przykład: given pogoda jest śniegowa, kiedy tworzę playlistę, na playliście jest piosenka "ain't no sunshine when she's gone".
  4. Wszystkie pozostałe rzeczy, takie jak tokeny, jwt, id, wszystko to można zaimplementować polimorficznie później.

Ja bym stworzył taki test:

Kopiuj
void playlistContainsSong_basedOnWeather_inUserLocation() {
  userInLocation("Kraków");
  weatherInLocation("Kraków", WeatherEnum.SNOW);
  generatePlaylist();
  assertContains("Aint' no sunshine when she's gone", playlistSongs());
}

Pytanie które ja mam, to czy Twój program ma jakąś logikę, np. decyduje jakimś algorytmem jaka piosenka ma być na podstawie jakiej pogody? Czy może ma być tylko takim "gołym" routerem requestów do spotify'a, czyli w zasadzie napisałbyś sobie klienta Spotify? 😄

Ornstein napisał(a):

Ok. Na ten moment podzieliłem to w taki sposób, że osobno obsługuję proces logowania do Spotify. Jeśli zakończy się on sukcesem, zapisuję token w Redis, a jako klucz wykorzystuję ID użytkownika pobrane z JWT. Osobno będzie tworzenie playlisty.

No spoko, możesz tak zrobić oczywiście, tylko szczerze mówiąc nie za wiele sensu widzę w pisaniu testów pod to, bo i tak nie kontrolujesz tego czy dostaniesz ten access token czy nie. Jak np. dostaniesz bana w spotify, to i tak nie masz żadnej kontroli nad tym, więc co Ci to da że te testy by nie przeszły?

Testy powinno się pisać pod rzeczy na które mamy wpływ, czyli w zasadzie tylko to co jest w ramach naszego projektu. Oczywiście możesz sobie zaprogramować co Twój program ma zrobić, jak spotify Cię odrzuci; ale to program powinien obsłużyć, nie testy.

Myślę że testy które napisałeś, tak jak mówił wcześniej @kelog po prostu testują czy mock zwrócił to co zamockowałeś 😉

edytowany 5x, ostatnio: Riddle
Ornstein
  • Rejestracja:około 2 lata
  • Ostatnio:około 14 godzin
  • Postów:111
0
Riddle napisał(a):

Pytanie które ja mam, to czy Twój program ma jakąś logikę, np. decyduje jakimś algorytmem jaka piosenka ma być na podstawie jakiej pogody? Czy może ma być tylko takim "gołym" routerem requestów do spotify'a, czyli w zasadzie napisałbyś sobie klienta Spotify? 😄

Algorytm jest w trakcie implementacji. Nie będzie tutaj cudów - jeśli pogoda będzie ponura, to muzyka typowo depresyjna. Jeśli będzie burza to jakiś mózgotrzep itp.

edytowany 1x, ostatnio: Ornstein
Riddle
Administrator
  • Rejestracja:prawie 15 lat
  • Ostatnio:mniej niż minuta
  • Lokalizacja:Laska, z Polski
  • Postów:10085
1
Ornstein napisał(a):

Algorytm jest w trakcie implementacji. Nie będzie tutaj cudów - jeśli pogoda będzie ponura, to muzyka typowo depresyjna. Jeśli będzie burza to jakiś muzgotrzep itp.

Okey, no widzisz. To ja Ci proponuję takie podejście:

Co do logiki aplikacji - pisz testy pod swój algorytm. Testy powinny wiedzieć o takich rzeczach jak: pogoda, lokacja, piosenka (tytuł), playlista (jako lista piosenek), humor, gatunek muzyki, rodzaj pogody. Testy nie powinny wiedzieć że piosenka jest ze spotify, nie powinny wiedzieć że playlista jest ze spotifya, najlepiej nic o spotify nie powinny wiedzieć. Możesz się posilkować kodem który podrzuciłem wyżej. Implementacja algorytmu nie powinna korzystać ze spotify'a bezpośrednio, ani nawet nie powinna korzystać z klienta http bezpośrednio. nie powinna też wiedzieć nic o tokenach. Schowaj całą integrację ze spotify za abstrakcją, np. za interfejsem:

Kopiuj
interface SpotifyIntegration {
  void login();
  boolean tryLoginOrFailure(); // return true on success, false on failure

  List<Integer> listSongIds();
  List<String> listPlaylistNames();
  void addSongToPlaylist(int songId, int playlistId); // int, long, String, typ sobie wybierz
}

Ewentualnie jak chcesz zachować gdzieś swój token, to wystaw go za pomocą interfejsu:

Kopiuj
interface SpotifyIntegration {
  String authenticateAndReturnToken();               // login and return access token
  void authenticateUsingExistingToken(String token); // use existing access token

  List<Integer> listSongIds();
  List<String> listPlaylistNames();
  void addSongToPlaylist(int songId, int playlistId); // int, long, String, typ sobie wybierz
}

I nic więcej. Wszelkie id, tokeny, etc. powinny być schowane w klasie implementacującej interfejs.

Co do integracji ze spotify - wiadomo że musisz ją jakoś lokalnie sprawdzić, czy działa, czy się ładuje. Sprawdzaj to lokalnie, ale testów pod to nie ma za bardzo sensu pisać. Na 99% Ci będą tylko przeszkadzać a nie pomagać. Żeby na prawdę one miały sens, takie akceptacyjne testy, to musiałbyś w gruncie rzeczy postawić lokalny sandbox spotify'a żeby móc do niego strzelać. No albo możesz też strzelać do prawdziwego spotify'a, ale to jest proszenie się o kłopoty bo testy będą failować z przyczyn niezależnych od Ciebie i przestaną być pomocne.

FAQ:

  • Ale czy to znaczy że jak będę miał buga w integracji ze spotify, to moje testy tego nie znajdą? No niestety tak 😕 Nie za bardzo jest sposób żeby to przetestować inny niż strzelenie do spotify'a albo do jakiegoś sandboxa spotify. Bez tego jedyne co możesz stworzyć to testy które testują mocki, tylko one są średnio pomocne, bo nie zawiele pomagają, trochę przeszkadzają, i niedostatecznie dobrze specyfikują zachowanie. Tak na prawdę takie testy testują tylko tyle że kod który napisałeś to kod który napisałeś.
edytowany 6x, ostatnio: Riddle
MA
Czy ja wiem, takie testy pod adapter, gdzie wkleisz praktycznie 1 do 1 odpowiedzi ze Spotify'a jednak cos sprawdzą. Wydaje mi się, że lepsze takie coś niż nic. Kodu domenowego nie ograniczają, bo wykorzystują tylko implementacje portu. Niestety tak jak piszesz, nie uchronią od wszystkiego - bo co np. jak zmieni się API, testy tego nie wykryją.
Riddle
Niby tak, ale weź pod uwagę że asercja testu i kod faktyczny byłyby w zasadzie identyczne, copy-paste. Więc jak się pomylisz w kodzie, to na 99.99% w asercja też się pomylisz i wtedy test przechodzi a działa źle.
ZI
Mysle ze uciekles za daleko w wycinaniu integracji. Wiremock powinien mockowac odpowiedzi spotify np zgrane w momencie testow "na zywo" i nimi odpowiadac. Mamy wtedy stabilne testy i jak cos sie wywali to znaczy ze blad jest u nas a nie akurat spotify mial czkawke.

Zarejestruj się i dołącz do największej społeczności programistów w Polsce.

Otrzymaj wsparcie, dziel się wiedzą i rozwijaj swoje umiejętności z najlepszymi.