Częstym wyzwaniem w testowaniu jest nie tylko JAK coś przetestować, ale CO można przetestować. Dlatego zrobiłem listę wszystkich rzeczy, które lubię testować w moich aplikacjach.
Wszystkie przykłady testów koncentrują się na koncepcjach testowania i mogą być stosowane do wszystkich struktur testowych. Moje przykłady są napisane za pomocą PEST.
Szukasz czegoś konkretnego? Wybierz to:
- Testowanie stanu odpowiedzi strony
- Testowanie tekstu odpowiedzi strony
- Testowanie widoku odpowiedzi strony
- Testowanie JSON odpowiedzi strony
- Testowanie z bazą danych
- Testowanie Walidacja
- Testowanie modeli / relacji
- Testowanie wiadomości e-mail
- Testowanie zawartości poczty
- Testowanie zadań i kolejek
- Powiadomienia o testowaniu
- Testowanie akcji
- Wyjątki testowania
- Jednostki testowe (testy jednostkowe)
- Fałszowanie połączeń HTTP
- Testowanie wywołań HTTP
- Uśmiewanie zależności
Nuta: Wszystkie przykłady można również znaleźć w dedykowanym repozytorium.
Testowanie statusu
odpowiedzi strony Testowanie odpowiedzi strony jest jednym z najprostszych testów do napisania; mimo to jest niezwykle przydatne.
Upewnia się, że strona odpowiada poprawnym kodem stanu HTTP, głównie odpowiedzią 200.
it('gives back a successful response for home page', function () {
$this->get('/')->assertOk();
});
To prosty test, ale wiedza o tym, że strona główna nie zgłasza błędu, ma kluczowe znaczenie. Tak więc, jeśli chcesz napisać swój pierwszy test, to jest ten, od którego należy zacząć.
Odniesienie: Dowiedz się więcej o testowaniu odpowiedzi na strony w oficjalnych dokumentach.
Testowanie tekstu
odpowiedzi strony Ten test jest podobny do testu odpowiedzi na pierwszą stronę. Testujemy również odpowiedź, ale tym razem interesuje nas treść odpowiedzi.
it('lists products', function () {
$firstProduct = Product::factory()->create();
$secondProduct = Product::factory()->create();
$this->get('/')
->assertOk()
->assertSeeTextInOrder([
$firstProduct->title,
$secondProduct->title,
]);
});
Tutaj upewniamy się, że widzimy tytuły naszych produktów na stronie głównej. Jest to przydatne, jeśli ładujesz produkty z bazy danych i upewniasz się, że są wyświetlane.
Tutaj również możesz być bardziej szczegółowy, na przykład gdy chcesz tylko pokazać released products
.
it('lists released products', function () {
$releasedProduct = Product::factory()
->released()
->create();
$draftProduct = Product::factory()
->create();
$this->get('/')
->assertOk()
->assertSeeText($releasedProduct->title)
->assertDontSeeText($draftProduct->title);
});
Pokazuje również, jak przetestować coś, co nie jest wyświetlane, co również może być pomocne. Ten test nie byłby tak pomocny, gdybyśmy mieli tylko statyczny tekst na stronie głównej.
Odniesienie: Dowiedz się więcej o testowaniu odpowiedzi na strony w oficjalnych dokumentach.
Testowanie widoku
odpowiedzi strony Oprócz testowania stanu odpowiedzi i zawartości można również przetestować zwrócony widok.
it('returns correct view', function() {
$this->get('/')
->assertOk()
->assertViewIs('home');
});
Możesz pójść jeszcze dalej i przetestować dane przekazywane do widoku.
it('returns correct view', function() {
$this->get('/')
->assertOk()
->assertViewIs('home')
->assertViewHas('products');
});
Odniesienie: Dowiedz się więcej o testowaniu odpowiedzi na strony w oficjalnych dokumentach.
Testowanie JSON odpowiedzi strony Często chcesz zwrócić dane JSON
z interfejsu API. Tutaj możesz użyć pomocników JSON Laravela, takich jak metoda assertJson
.
it('returns all products as JSON', function () {
$product = Product::factory()->create();
$anotherProduct = Product::factory()->create();
$this->post('api/products')
->assertOk()
->assertJson([
[
'title' => $product->title,
'description' => $product->description,
],
[
'title' => $anotherProduct->title,
'description' => $anotherProduct->description,
],
]);
});
Odniesienie: Dowiedz się więcej o testowaniu odpowiedzi na strony w oficjalnych dokumentach.
Testowanie z bazą danych Ponieważ przechowujemy dane w bazie danych
, chcemy mieć pewność, że dane są poprawnie przechowywane. W tym miejscu Laravel może ci pomóc z kilkoma przydatnymi pomocnikami asertywnymi.
it('stores a product', function () {
$this->actingAs(User::factory()->create())
->post('product', [
'title' => 'Product name',
'description' => 'Product description',
])->assertSuccessful();
$this->assertDatabaseCount(Product::class, 1);
$this->assertDatabaseHas(Product::class, [
'title' => 'Product name',
'description' => 'Product description',
]);
});
Przykład zapewnia, że produkt jest tworzony i przechowywany w bazie danych dla naszej trasy pocztowej.
Odniesienie: Znajdź wszystkie potwierdzenia bazy danych w oficjalnej dokumentacji.
Testowanie Walidacja Walidacja
jest kluczową częścią wielu aplikacji. Chcesz mieć pewność, że można przesyłać tylko prawidłowe dane. Domyślnie Laravel wysyła błędy walidacji z powrotem do użytkownika, co możemy sprawdzić za pomocą assertInvalid
metody.
it('requires the title', function () {
$this->actingAs(User::factory()->create())
->post('product', [
'description' => 'Product description',
])->assertInvalid(['title' => 'required']);
});
it('requires the description', function () {
$this->actingAs(User::factory()->create())
->post('product', [
'title' => 'Product name',
])->assertInvalid(['description' => 'required']);
});
W przypadku wielu reguł sprawdzania poprawności korzystanie z zestawów danych może być bardzo pomocne. To może bardzo oczyścić twoje testy.
it('requires title and description tested with a dataset', function($data, $error) {
$this->actingAs(User::factory()->create())
->post('product', $data)->assertInvalid($error);
})->with([
'title required' => [['description' => 'text'], ['title' => 'required']],
'description required' => [['title' => 'Title'], ['description' => 'required']],
]);
Odniesienie: Dowiedz się więcej o potwierdzeniach sprawdzania poprawności w oficjalnych dokumentach.
Testowanie modeli / relacji
Po pierwsze, lubię testować każdą relację modelu. Mówiąc dokładniej, chcemy przetestować funkcjonalność związku; not
to właśnie robi Laravel. Chcemy mieć pewność, że relacje są zdefiniowane.
it('has products', function () {
$user = User::factory()
->has(Product::factory())
->create();
$products = $user->products;
expect($products)
->toBeInstanceOf(Collection::class)
->first()->toBeInstanceOf(Product::class);
});
Programiści mają różne opinie na temat tego, jaka logika powinna być obsługiwana w modelu i to jest w porządku. Ale jeśli go dodasz, pamiętaj również, aby go przetestować.
it('only returns released courses for query scope', function () {
Course::factory()->released()->create();
Course::factory()->create();
expect(Course::released()->get())
->toHaveCount(1)
->first()->id->toEqual(1);
});
Innym przykładem może być model akcesora taki jak:
protected function firstName(): Attribute
{
return Attribute::make(
get: fn (string $value) => ucfirst($value),
);
}
A test wyglądałby tak:
it('capitalizes the first character of the first name', function () {
$user = User::factory()->create(['first_name' => 'christoph'])
expect($user->first_name)
->toBe('Christoph');
});
Testowanie Wysyłanie wiadomości
Laravel zapewnia wielu pomocników testujących, szczególnie podczas korzystania z fasad.
class PublishPodcastController extends Controller
{
public function __invoke(Podcast $podcast)
{
Mail::to($podcast->author)->send(new PodcastPublishedMail());
}
}
Na końcu tego kontrolera wysyłamy wiadomość e-mail. W naszym teście możemy trafić do kontrolera przez punkt końcowy i upewnić się, że ten e-mail would have been
został wysłany.
it('sends email to podcast author', function() {
Mail::fake();
$podcast = Podcast::factory()->create();
$this->post(route('publish-podcast', $podcast));
Mail::assertSent(PodcastPublishedMail::class);
});
Zawsze uruchamiaj metodę Mail::fake()
na początku testów podczas testowania wiadomości e-mail. Dzięki temu żadna rzeczywista wiadomość e-mail nie jest wysyłana do użytkownika.
Większość metod pomocniczych, takich jak assertSent
również akceptuje wywołanie zwrotne jako drugi argument. W naszym przypadku otrzymuje obiekt pocztowy. Zawiera wszystkie dane e-mail, takie jak wiadomość e-mail, na którą należy ją wysłać.
Pozwala to na jeszcze więcej twierdzeń, takich jak "adres" "do" wiadomości e-mail.
Mail::assertSent(PodcastPublishedMail::class, function(PodcastPublishedMail $mail) use ($podcast) {
return $mail->hasTo($podcast->author->email);
});
Odniesienie: Dowiedz się więcej o testowaniu materiałów do wysyłki w oficjalnych dokumentach.
Testowanie zawartości
poczty Warto również przetestować zawartość wiadomości e-mail. Jest to szczególnie przydatne, gdy masz wiele wiadomości e-mail w aplikacji. Chcesz się upewnić, że zawartość jest poprawna.
it('contains the product title', function () {
$product = Product::factory()->make();
$mail = new PaymentSuccessfulMail($product);
expect($mail)
->assertHasSubject('Your payment was successful')
->assertSeeInHtml($product->title);
});
Odniesienie: Dowiedz się więcej o wszystkich innych twierdzeniach dotyczących testowania zawartości poczty w oficjalnych dokumentach.
Testowanie zadań i kolejek
Lubię testować zadania i kolejki osobno, zaczynając od zewnątrz. Oznacza to, że testuję, że zadanie jest wypychane do kolejki.
it('dispatches an import products job', function () {
Queue::fake();
$this->post('import');
Queue::assertPushed(ImportProductsJob::class);
});
Gwarantuje to, że moje zadanie zostanie wypchnięte do kolejki dla określonego wyzwalacza, takiego jak trafienie w punkt końcowy. Ponownie, dba Queue::fake()
o to, aby nie pchać pracy. W tym momencie nie chcemy uruchamiać zadania.
Ale nadal musimy przetestować pracę, prawda? Oczywiście. Zawiera kluczową logikę tej funkcji:
it('imports products', function() {
(new ImportProductsJob)->handle();
$this->assertDatabaseCount(Product::class, 50);
})
Ten nowy test koncentruje się na pracy i na tym, co powinien zrobić. Uruchamiamy zadanie bezpośrednio, wywołując handle
na nim, co ma każda praca.
Testowanie powiadomień
Powiadomienia doskonale nadają się do informowania użytkowników o ważnych zdarzeniach. Pamiętaj, aby je również przetestować:
it('sends notification about new product', function () {
Notification::fake();
$user = User::factory()->create();
$product = Product::factory()->create();
$this->artisan(InformAboutNewProductNotification::class, [
'productId' => $product->id,
'userId' => $user->id,
]);
Notification::assertSentTo(
[$user], NewProductNotification::class
);
});
W powyższym przykładzie testujemy powiadomienie wysyłane do użytkownika po utworzeniu nowego produktu. Używamy tej artisan
metody do wyzwalania powiadomienia. Jest to świetny sposób na przetestowanie powiadomień wyzwalanych przez polecenie.
Ponownie, istnieje fake
metoda fasady powiadomienia, która zapewnia, że żadne powiadomienie nie jest wysyłane.
Odniesienie: Dowiedz się więcej o testowaniu powiadomień w oficjalnych dokumentach.
Testowanie akcji
Akcje to tylko proste klasy, które mają jedno określone zadanie. Są świetnym sposobem na uporządkowanie kodu i oddzielenie logiki od kontrolerów, aby utrzymać je w czystości. Ale jak je przetestować?
Zacznijmy od początku. Po pierwsze, chcemy sprawdzić, czy nasze działanie jest wywoływane po osiągnięciu określonego punktu końcowego.
it('calls add-product-to-user action', function () {
$this->mock(AddProductToUserAction::class)
->shouldReceive('handle')
->atLeast()->once();
$product = Product::factory()->create();
$user = User::factory()->create();
$this->post("purchase/$user->id/$product->i");
});
Możemy to zrobić, kpiąc z naszej klasy działania i oczekując, że metoda uchwytu zostanie wywołana. Ale, znowu, nie jesteśmy tutaj zainteresowani tym, co robi nasze działanie; Chcemy mieć pewność, że zostanie wywołany, gdy uderzymy w nasz kontroler zakupów.
Aby to zadziałało, musimy upewnić się, że pojemnik rozwiązuje nasze działanie.
class PurchaseController extends Controller
{
public function __invoke(User $user, Product $product): void
{
app(AddProductToUserAction::class->handle($user, $product);
}
}
Wtedy też możemy przetestować samą akcję. Podobnie jak w przypadku pracy, wywołujemy handle
metodę wyzwalania akcji.
it('adds product to user', function () {
$product = Product::factory()->create();
$user = User::factory()->create();
(new AddProductToUserAction())->handle($user, $product);
expect($user->products)
->toHaveCount(1)
->first()->id->toEqual($product->id);
});
Testowanie wyjątków Czasami dobrze jest, gdy wyjątek
jest zgłaszany, ponieważ celowo chcemy zatrzymać wykonywanie naszego kodu. To też możemy przetestować.
it('stops if at least one account not found', function () {
$this->artisan(MergeAccountsCommand::class, [
'userId' => 1,
'userToBeMergedId' => 2,
]);
})->throws(ModelNotFoundException::class);
Możemy połączyć metodę throws
z naszym testem w PEST. Zapewni to, że wyjątek zostanie zgłoszony.
Jednostki testowe (testy jednostkowe)
Testy jednostkowe doskonale nadają się do testowania małych fragmentów kodu, takich jak pojedyncza metoda. Nie występują żadne inne zależności. Dzięki temu są bardzo szybkie i łatwe do napisania.
Nasz przykład dotyczy obiektu danych. Zawiera metodę, która tworzy nowe wystąpienie z ładunku elementu webhook.
class UserData
{
public function __construct(
public string $email,
public string $name,
public string $country,
)
{}
public static function fromWebhookPayload(array $webhookCallData): UserData
{
return new self(
$webhookCallData['client_email'],
$webhookCallData['client_name'],
$webhookCallData['client_country'],
);
}
}
W odpowiednim teście sprawdzamy tylko to, co zwraca ta metoda.
it('creates UserData object from paddle webhook call', function () {
$payload = [
'client_email' => '[email protected]',
'client_name' => 'Christoph Rumpel',
'client_country' => 'AT',
];
$userData = UserData::fromWebhookPayload($payload);
expect($userData)
->email->toBe('[email protected]')
->name->toBe('Christoph Rumpel')
->country->toBe('AT');
});
Fałszowanie wywołań HTTP Czasami musisz wykonywać wywołania
HTTP w aplikacji. Może to być pobieranie danych z zewnętrznego interfejsu API lub wysyłanie danych do innej usługi. Często chcesz sfałszować te połączenia w swoich testach, aby nie musieć polegać na usłudze zewnętrznej.
it('import product', function () {
Http::fake();
});
Metoda fake
na fasadzie HTTP
zapewni, że nie zostanie wykonane żadne prawdziwe połączenie, a odpowiedź będzie zawsze kodem 200
stanu.
Ale możesz też być bardziej konkretny. Na przykład testujemy akcję, która pobiera dane z zewnętrznego interfejsu API i zapisuje je w bazie danych.
it('imports product', function() {
Http::fake([
'https://christoph-rumpel.com/import' => Http::response([
'title' => 'My new product',
'description' => 'This is a description',
]),
]);
$user = User::factory()->create();
(new ImportProductAction)->handle($user);
$this->assertDatabaseHas(Product::class, [
'title' => 'My new product',
'description' => 'This is a description',
]);
});
Testowanie połączeń
HTTP Oprócz fałszowania wywołań HTTP można również sprawdzić, czy wykonano określone połączenie. Jest to przydatne, gdy chcesz mieć pewność, że kod wykonuje prawidłowe wywołania.
it('make the right call', function () {
Http::fake();
$user = User::factory()->create();
(new ImportProductAction)->handle($user);
Http::assertSent(function ($request) {
return $request->url() === 'https://christoph-rumpel.com/import'
&& $request['accessToken'] === '123456';
});
});
Odniesienie: Dowiedz się więcej o fałszowaniu wywołań HTTP w oficjalnych dokumentach.
Pozorowanie zależności
Podczas pracy z kodem z zależnościami pomocne może być ich wyśmiewanie. Pozwoli to skoncentrować się na logice kodu, a nie na zależnościach. Oznacza to również mocking can be useful for any kind of tests
.
Zrobiliśmy to już podczas testowania naszych klas akcji, ale działa to z każdą zależnością. W poniższym przykładzie mamy kontroler z dwiema zależnościami: dostawcą płatności i mailerem.
class PaymentController extends Controller
{
public function __invoke(PaymentProvider $paymentProvider, Mailer $mailer)
{
$paymentProvider->handle();
$mailer->to(auth()->user())->send(new PaymentSuccessfulMail);
}
}
W naszym teście chcemy skupić się na sprawdzeniu, czy wysyłana jest prawidłowa wiadomość e-mail. Dlatego możemy kpić z dostawcy płatności i oczekiwać, handle
że metoda zostanie wywołana. W rezultacie nasz test nie zakończy się niepowodzeniem, nawet jeśli rzeczywisty dostawca płatności nigdy nie zostanie wezwany.
it('sends payment successful mail', function () {
Mail::fake();
$this->mock(PaymentProvider::class)
->shouldReceive('handle')
->once();
$this->post('payment');
Mail::assertSent(PaymentSuccessfulMail::class);
});
Wniosek
: Stworzenie w głowie listy wszystkiego, co lubisz testować w swoich aplikacjach, jest niezbędne. Ale oczywiście, ucząc się nowych koncepcji kodowania, musisz także nauczyć się nowych technik testowania. Twoja lista będzie się z czasem powiększać.
Jeśli uważasz, że brakuje mi czegoś ważnego, daj mi znać na Twitterze. Zawsze chętnie uczę się nowych rzeczy.