Unit test Angular/ngrx/store/action-effect

7 views
Skip to first unread message

mick...@posteo.net

unread,
Jan 9, 2025, 9:11:59 AMJan 9
to socr...@googlegroups.com
Domanda: c'è qualcuno in ascolto che ha esperienza nell'ambito in
oggetto?

Da un anno a questa parte sto lavorando allo sviluppo di un grosso
applicativo, il cui front-end è implementato in Angular, ma il livello
medio di esperienza del team è piuttosto basso, e anche quelli che
tenngono su un po' tale media, ogni tanto li vedo un po' in difficoltà a
rispondere alle mie domande.
Io tendenzialmente prendo spunto dal codice che c'è già, e faccio
riferimento alla documentazione ufficiale per approfondire i dettagli,
ma alle volte incappo in situazioni che non riesco a capire, e non so
nemmeno come formulare una ricerca sul web per trovare dei chiarimenti,
ma se considerate che quando ho iniziato, anche solo javascript per me
era un mistero.

In questo caso, ad esempio, sto scrivendo i test unitari degli effects
di un modulo, ma mi sono reso conto che uno di questi effetti (creato
con "dispatch: false") viene invocato due volte: una prima volta con i
mock del caso, ed una seconda volta (quando viene eseguito il test
successivo, qualsiasi esso sia - sì: ho provato a cambiarne l'ordine)
senza tali mock, quindi con gli oggetti effettivi.
Ad intuito, è come se la action che triggera l'effect in questione,
rimanesse pendente (non venisse "consumato" dalla pipe dell'observable),
e nel test successivo, venisse triggero nuovamente lo stesso effect.
Se c'è qualcuno che se ne intende, posso mostrare il pesso di codice in
questione, e magari voi saprete illuminarmi su cosa sto sbagliando...

--
Mick

Mario Alexandro Santini

unread,
Jan 9, 2025, 10:37:34 AMJan 9
to socr...@googlegroups.com
On Thu, Jan 9, 2025 at 3:12 PM <mick...@posteo.net> wrote:
Domanda: c'è qualcuno in ascolto che ha esperienza nell'ambito in
oggetto?

Ciao Mick,
 
purtroppo Angular non fa parte della mia cassetta degli attrezzi.

Immagino tu ti riferisca ad Angular vXY e non ad AngularJS, corretto?


In questo caso, ad esempio, sto scrivendo i test unitari degli effects
di un modulo, ma mi sono reso conto che uno di questi effetti (creato
con "dispatch: false") viene invocato due volte: una prima volta con i
mock del caso, ed una seconda volta (quando viene eseguito il test
successivo, qualsiasi esso sia - sì: ho provato a cambiarne l'ordine)
senza tali mock, quindi con gli oggetti effettivi.

Potrebbe essere un caso di setup e cleanup dei test.
Se imposti il mock prima del primo test, è plausibile che la stessa azione vada fatta prima dell'esecuzione di ogni test, a meno che tu non lo faccia lo stesso.
Da quanto scrivi ho capito che hai problemi nell'esecuzione del secondo test.
 
Ad intuito, è come se la action che triggera l'effect in questione,
rimanesse pendente (non venisse "consumato" dalla pipe dell'observable),
e nel test successivo, venisse triggero nuovamente lo stesso effect.
Se c'è qualcuno che se ne intende, posso mostrare il pesso di codice in
questione, e magari voi saprete illuminarmi su cosa sto sbagliando...

Alcuni test runner fanno il mock delle funzioni setTimeout e setInterval e così via, quindi è possibile che accada quello che intendi. Prova a verificare se è questo il caso.
In genere ci sono dei metodi per far eseguire tutti i task schedulati.

Io direi che puoi anche provare a postare il codice, magari qualcuno potrebbe riuscire a darti una mano comunque.
 
--
Mick


Mario

mick...@posteo.net

unread,
Jan 9, 2025, 12:10:31 PMJan 9
to socr...@googlegroups.com, Mario Alexandro Santini


On 09.01.2025 16:37, Mario Alexandro Santini wrote:
> On Thu, Jan 9, 2025 at 3:12 PM <mick...@posteo.net> wrote:
>
>> Domanda: c'è qualcuno in ascolto che ha esperienza nell'ambito in
>> oggetto?
>
> Ciao Mick,
>
> purtroppo Angular non fa parte della mia cassetta degli attrezzi.
>
> Immagino tu ti riferisca ad Angular vXY e non ad AngularJS, corretto?
>
Esatto

>
> Da quanto scrivi ho capito che hai problemi nell'esecuzione del
> secondo test.
>
In un certo senso. Il problema è che il secondo test, qualunque esso sia
(come detto ho provato a cambiare la sequenza), risente di un residuo di
stato del primo, mentre per come sono strutturati, dovrebbero essere
tutti indipendenti.

Domani provo a sintetizzare il codice, e mandarvelo, sperando che
qualcuno ci capisca.
--
mick

mick...@posteo.net

unread,
Jan 10, 2025, 3:07:35 AMJan 10
to socr...@googlegroups.com, Mario Alexandro Santini
On 09.01.2025 16:37, Mario Alexandro Santini wrote:
>
> Io direi che puoi anche provare a postare il codice, magari qualcuno
> potrebbe riuscire a darti una mano comunque.
>
Ho posto la domanda su stackoverflow [1], se volete darle un'occhiata.

[1]
https://stackoverflow.com/questions/79344981/angular-unit-test-of-ngrx-effects-actions-not-purged
--
Mick

mick...@posteo.net

unread,
Jan 10, 2025, 5:16:09 AMJan 10
to socr...@googlegroups.com


On 10.01.2025 10:25, Mario Alexandro Santini wrote:
> On Fri, Jan 10, 2025 at 9:07 AM <mick...@posteo.net> wrote:
>
>> Ho posto la domanda su stackoverflow [1], se volete darle
>> un'occhiata.
>> [1]
>> https://stackoverflow.com/questions/79344981/angular-unit-test-of-ngrx-effects-actions-not-purged
>
> Ho l'impressione che il problema sia proprio di setup dei test.
>
> In particolare, credo che il problema sia la variabile actions$.
>
> La avvalori dopo aver fatto l'injection, perché quello sta nel
> beforeEach, mentre la actions$ è nel test.
>
Non è esatto, ma hai toccato uno dei punti che non mi sono chiari:
avvaloro action$ nel punto in cui la dichiaro, come "new Observable()".
Poi all'interno del test sostituisco il valore con una of(...).

Questo è un pattern che ho copiato da molti altri test presenti nello
stesso progetto, e che fatico a capire! Quando viene eseguito il test
(la lambda inserita nella it( ) ), il injection-context (chiamiamolo
così) è già inizializzato (dalla lambda specificata in beforeEach(...)
), quindi come fa l'istruzione "action$=of(...)" ad influenzarlo?
Da una analisi teorica mi aspetterei che in ogni test, la action$
contenga il valore impostato nel test precedente. In realtà non è così,
visto che i test passano tutti. L'unica cosa che vedo io è che l'effetto
di una action viene eseguito due volte: nel suo test, e in quello
successivo.
--
Mick

tia...@gmail.com

unread,
Jan 10, 2025, 5:52:36 AMJan 10
to Socraten
Ciao Mick,

Quando viene eseguito il test
(la lambda inserita nella it( ) ), il injection-context (chiamiamolo
così) è già inizializzato (dalla lambda specificata in beforeEach(...)
), quindi come fa l'istruzione "action$=of(...)" ad influenzarlo?
Da una analisi teorica mi aspetterei che in ogni test, la action$
contenga il valore impostato nel test precedente. In realtà non è così,
visto che i test passano tutti. L'unica cosa che vedo io è che l'effetto
di una action viene eseguito due volte: nel suo test, e in quello
successivo.


Allora secondo me, è vero che la action$ contiene il valore del test precendente, però solo al inizio del test.
Quando la variabile action$ viene cambiata, questo ha un effetto a tutte le istruzioni successive, quindi probabilmente l'injection funziona in modo lazy. 🤷‍♂️

Poi non so esattamente perche l'azione viene eseguita un'altra volta, però so che `of` crea un Observable "cold", quindi ogni chiamata a "subscribe" esegue l'azione.
Forse c'è un subscribe quando viene creato l'effetto?

La cosa più semplice qua sarebbe un single-step debugging, o fare un log con stack trace quando viene eseguito l'azione.

Matthias

mick...@posteo.net

unread,
Jan 15, 2025, 4:54:14 AMJan 15
to socr...@googlegroups.com


On 10.01.2025 11:16, mick...@posteo.net wrote:
> On 10.01.2025 10:25, Mario Alexandro Santini wrote:
>> On Fri, Jan 10, 2025 at 9:07 AM <mick...@posteo.net> wrote:
>>
>>> Ho posto la domanda su stackoverflow [1], se volete darle
>>> un'occhiata.
>>> [1]
>>> https://stackoverflow.com/questions/79344981/angular-unit-test-of-ngrx-effects-actions-not-purged
>>
>> Ho l'impressione che il problema sia proprio di setup dei test.
>>
>> In particolare, credo che il problema sia la variabile actions$.
>>
>> La avvalori dopo aver fatto l'injection, perché quello sta nel
>> beforeEach, mentre la actions$ è nel test.
>>
> Non è esatto, ma hai toccato uno dei punti che non mi sono chiari:
> avvaloro action$ nel punto in cui la dichiaro, come "new
> Observable()". Poi all'interno del test sostituisco il valore con una
> of(...).
>
Piccolo aggiornamento, per chi fosse curioso.
Nonostante l'pproccio iniziale fosse lo stesso suggerito della
documentazione ufficiale [1], ho voluto provare la soluzione suggeritami
su stackoverflow [2]: sostanzialmente ho definito action$ come Subject,
invece che come Observable, il quale ha un metodo next() per emettere il
valore successivo. Lo istanzio nella beforeEach, prima di inizializzare
l'injection-context e lo uso durante il test (invece che sostituirlo
all'interno del test, come facevo prima).
In questo modo l'effect viene comunque eseguito due volte, ma
all'interno dello stesso test. Dal momento che la situazione non
compromette l'esito della batteria di test, ho deciso che per il momento
lo lascio così, ma cosa succeda sotto il cofano mi appare ancora
misterioso.

In compenso, rimuginandoci sopra, forse ho capito come faceva a
funzionare quello di prima. Se io definisco una variabile, e poi
definisco una lambda che la utilizza, quello che viene passato alla
lambda è un riferimento ad essa; nel caso di un oggetto, che già di per
sè è un riferimento, quello che viene passato è un "riferimento al
riferimento" (concetto che non pi pare esista, per un utilizzo diretto,
giusto?), quindi scrivendo "action$=of(...)", anche la action$ usata
nella beforeEach risulta modificata. Questo l'ho intuito dal fatto che
anche variabili non riferimento (come number) rimangono allineate.

Ma allora mi domando: se io definisco una variabile, ed una lambda che
ne fa uso, e poi esco dallo scope della variabile, mentre la lambda è
ancora in uso, quest'ultima si ritrova con un riferimento non valido?
Oppure ci sono dei meccanismi di reference-counting, per cui la
variabile rimane valida anche dopo che sono uscito dalla funzione in cui
l'ho definita?
--
Mick


[1] https://ngrx.io/guide/effects/testing
[2]
https://stackoverflow.com/questions/79344981/angular-unit-test-of-ngrx-effects-actions-not-purged

Mario Alexandro Santini

unread,
Jan 15, 2025, 5:16:13 AMJan 15
to socr...@googlegroups.com
On Wed, Jan 15, 2025 at 10:54 AM <mick...@posteo.net> wrote:

Piccolo aggiornamento, per chi fosse curioso.
Nonostante l'pproccio iniziale fosse lo stesso suggerito della
documentazione ufficiale [1], ho voluto provare la soluzione suggeritami
su stackoverflow [2]: sostanzialmente ho definito action$ come Subject,
invece che come Observable, il quale ha un metodo next() per emettere il
valore successivo. Lo istanzio nella beforeEach, prima di inizializzare
l'injection-context e lo uso durante il test (invece che sostituirlo
all'interno del test, come facevo prima).
In questo modo l'effect viene comunque eseguito due volte, ma
all'interno dello stesso test. Dal momento che la situazione non
compromette l'esito della batteria di test, ho deciso che per il momento
lo lascio così, ma cosa succeda sotto il cofano mi appare ancora
misterioso.


Ciao Mick,

Credo ti manchi il chiamare .complete() in un afterEach(), da quanto ho letto nella risposta.

 

Ma allora mi domando: se io definisco una variabile, ed una lambda che
ne fa uso, e poi esco dallo scope della variabile, mentre la lambda è
ancora in uso, quest'ultima si ritrova con un riferimento non valido?
Oppure ci sono dei meccanismi di reference-counting, per cui la
variabile rimane valida anche dopo che sono uscito dalla funzione in cui
l'ho definita?

La closure (in JS la funzione con il suo contesto), cattura la variabile e ne mantiene lo scope.
Quindi in memoria la variabile rimane finché esiste la funzione.


 
--
Mick


Mario

mick...@posteo.net

unread,
Jan 15, 2025, 5:59:48 AMJan 15
to socr...@googlegroups.com

On 15.01.2025 11:15, Mario Alexandro Santini wrote:
> On Wed, Jan 15, 2025 at 10:54 AM <mick...@posteo.net> wrote:
>
> Credo ti manchi il chiamare .complete() in un afterEach(), da quanto
> ho letto nella risposta.
>
In realtà action$.complete() va chiamata all'interno di ogni singolo
test, per come è strutturato (il termine del test è legato
all'esaurimento di action$, per cui senza chiamare la complete, il test
non viene mai terminato, e afterEach() non viene mai chiamato (o viene
chiamato solo allo scadere del timeout, che comunque per me non va bene,
quindi non ho approfondito).

Rimane il mistero che l'effect viene eseguito due volte, cosa che ho
appurato incrementando e stampando un contatore all'interno del mock di
un metodo chamato dall'effect (Router.navigate() ).
--
Mick
Reply all
Reply to author
Forward
0 new messages