Willkommen auf myCSharp.de! Anmelden | kostenlos registrieren
 | Suche | FAQ

Hauptmenü
myCSharp.de
» Startseite
» Forum
» Suche
» Regeln
» Wie poste ich richtig?

Mitglieder
» Liste / Suche
» Wer ist online?

Ressourcen
» FAQ
» Artikel
» C#-Snippets
» Jobbörse
» Microsoft Docs

Team
» Kontakt
» Cookies
» Spenden
» Datenschutz
» Impressum

  • »
  • Community
  • |
  • Diskussionsforum
Testen einer API Wrapper Library
Duesmannr
myCSharp.de - Member



Dabei seit:
Beiträge: 153
Herkunft: Münster

Themenstarter:

Testen einer API Wrapper Library

beantworten | zitieren | melden

Nabend,
falls dies nicht der richtige Bereich sein sollte, gerne verschieben.

Ich habe eine Library für die API von TMDB geschrieben in .NET 6.
API Doku ist hier zu finden.
Dabei nutze ich Refit um die Requests zu senden.

Für die Library würde ich gerne Tests schreiben um die Funktionalität der Library zu gewährleisten.
Ich nutze das Test Framework von Microsoft "MSTest".

Ich möchte gerne Wissen ob mein Vorgehen dabei das richtige ist.

Es gibt mehrere Endpunkte wo man Informationen holen kann, zB. von Filmen/Serien/Personen.
Da würde ich einen Test schreiben wo der Service authentifiziert ist und eine richtige Film Id übergibt. Sowie der Service nicht authentifiziert ist und eine Exception wirft und ich eine Id übergebe die nicht existiert.

Es gibt auch einen Endpunkt wo man Filme und Serien als Favorit markieren kann. Meine Library Implementation sieht wie folgt aus:


private async Task<Response> MarkAsFavoriteAsync(int accountId, int mediaId, AccountMediaType mediaType, bool favorite)
{
    this.CheckAuthentication();
    this.CheckSession();

    MarkAsFavoriteRequest markAsFavoriteRequest = new(mediaType.GetString(), mediaId, favorite);

    try
    {
        return this._authenticationType == AuthenticationType.ApiKey
                ? await this.AccountApi.MarkAsFavoriteAsync(accountId, this._sessionId!, markAsFavoriteRequest, this._token).ConfigureAwait(false)
                : await this.AccountApi.MarkAsFavoriteAsync(accountId, this._sessionId!, markAsFavoriteRequest).ConfigureAwait(false);
    }
    catch (ApiException ex)
    {
        if(string.IsNullOrWhiteSpace(ex.Content))
        {
            return new();
        }

        return JsonSerializer.Deserialize<Response>(ex.Content) ?? new();
    }
}

AccountApi ist das Interface um den API Call mittels Refit zu senden.
Die Methode wird von dieser hier aufgerufen:


public async Task<Response> MarkMovieAsFavoriteAsync(int accountId, int movieId, bool favorite)
{
    return await this.MarkAsFavoriteAsync(accountId, movieId, AccountMediaType.Movie, favorite).ConfigureAwait(false);
}

Die 2. Methode ist in einem Interface angegeben und das Interface benutze ich in der Test Klasse.
Jetzt weiß ich nicht, wie ich diese Methode am besten teste.
Wenn ich einen Film als Favorit markiere, bekomme ich die Rückmeldung, dass ich den Film erfolgreich als Favorit markiert habe.
Sende ich den Request jetzt nochmal, bekomme ich die Rückmeldung, dass der Eintrag geupdated worden ist.
Müsste ich die Markierung nach dem Ende des Tests nicht zurücksetzen? Wie handhabt man das bei einer öffentlichen API?
Und wie teste ich das in diesem konkreten Fall am besten?

Das sind zwei Tests die ich für die Methode geschrieben habe:


[TestMethod]
[TestProperty(nameof(AccountService), nameof(AccountService_MarkMovieAsFavorite))]
[TestCategory(IntegrationTestCategoryName)]
public async Task AccountService_MarkMovieAsFavorite()
{
	//Arrange
	this._accountService.UseExistingSessionId(this.AccountSession!);
	int movieId = 60735;
	bool favorite = true;
	bool success = true;

	//Act
	Response response = await this._accountService.MarkMovieAsFavoriteAsync(this.AccountId, movieId, favorite);

	//Assert
	Assert.IsNotNull(response);
	Assert.AreEqual(success, response.Success);
}

[TestMethod]
[TestProperty(nameof(AccountService), nameof(AccountService_MarkMovieAsFavorite_InvalidValues))]
[TestCategory(IntegrationTestCategoryName)]
public async Task AccountService_MarkMovieAsFavorite_InvalidValues()
{
	//Arrange
	this._accountService.UseExistingSessionId(this.AccountSession!);
	int movieId = 0;
	bool favorite = true;
	bool success = false;

	//Act
	Response response = await this._accountService.MarkMovieAsFavoriteAsync(this.AccountId, movieId, favorite);

	//Assert
	Assert.IsNotNull(response);
	Assert.AreEqual(success, response.Success);
}

Schönen Abend noch!
René
private Nachricht | Beiträge des Benutzers
Palladin007
myCSharp.de - Member

Avatar #avatar-4140.png


Dabei seit:
Beiträge: 1.816
Herkunft: Düsseldorf

beantworten | zitieren | melden

Du testest die externe API mit, das willst Du nicht, denn da ändern sich natürlich die Daten und es ist langsam.
Bei UnitTests willst Du das testen, worüber Du die Kontrolle hast und deine Kontrolle endet bei Refit.

Ich würde also in den UnitTests die Refit-Interfaces mocken, immer so, wie es deine Tests brauchen.
Dann kannst Du performant ohne zuverlässig reproduzierbar deine Tests aufbauen.

By the way:
Guck dir mal xUnit an, ich mag's lieber.
private Nachricht | Beiträge des Benutzers
Abt
myCSharp.de - Team

Avatar #avatar-4119.png


Dabei seit:
Beiträge: 16.026

beantworten | zitieren | melden

Du hast da ja mehrere Faktoren.

Zum einen hast Du Refit als API Beschreibung, zum anderen willst Du wohl, wenn ich Dich richtig versteh, auch einen interaktiven Client zur Verfügung stellen, der auch logische Aufgaben erledigt.
Refit selbst ist ja deklarativ, kann man aber mit MockHttp testen.
Deinen Client, also die Logik, kannst Du ebenfalls "normal" über Unit Tests abdecken, zusammen mit Hilfsmitteln wie Moq, XUnit, FluentAssertions, AutoFixture (je nach Gefallen, das sind meine Standard-Kandidaten fürs Testen).

Feedback zum Code: das wird so definitiv aufwendig zu testen ;-)
Modularier Deinen Code und verwende die Hilfsmittel von Refit, dann musst Du auch nicht so Zeug behandeln wie


    this.CheckAuthentication();
    this.CheckSession();
weil dafür gibts die HttpHandler / DelegatingHandler. Genauso für das ganze Thema Exception Handling etc.
private Nachricht | Beiträge des Benutzers
Duesmannr
myCSharp.de - Member



Dabei seit:
Beiträge: 153
Herkunft: Münster

Themenstarter:

beantworten | zitieren | melden

Danke für euren Input.

Dann werde ich wohl auf XUnit umsteigen und werde mir die HttpHandler genauer ansehen.
Ans Mocken hatte ich gar nicht gedacht, aber muss man ja, wenn man die externe API nicht testen will.

@Abt ich habe noch keine Idee wie ich das Exception Handling mit Refit machen soll. Die Snippets auf der Github Seite helfen mir da bedingt weiter.
Was ich damit meine ist, was ist der Way2Go für Exceptions in einer Library? Die ApiException sollte ich ja schon behandeln oder nicht?
Werfe ich dann eine neue custom Exception?
private Nachricht | Beiträge des Benutzers
Abt
myCSharp.de - Team

Avatar #avatar-4119.png


Dabei seit:
Beiträge: 16.026

beantworten | zitieren | melden

So wie man es in .NET immer macht: generelle Exceptions abfangen und eigene Exceptions verwenden:
https://docs.microsoft.com/en-us/dotnet/standard/exceptions/
In den meisten Fällen folgt man den Gedanken, dass man selbst die Lib (hier Refit) wegabstrahiert, um nach Außen keine Abhängigkeit zu schaffen, die Du hättest, wenn Du einfach die ApiException roh weiter geben würdest.

Heisst in Deinem Fall: ApiException behandeln und in spezifische Exceptions übersetzen.
Dafür hat Refit einen Mechanismus, steht auch in den Docs.
private Nachricht | Beiträge des Benutzers