Movatterモバイル変換


[0]ホーム

URL:


Skip to content
DEV Community
Log in Create account

DEV Community

Daniel Cardona Rojas
Daniel Cardona Rojas

Posted on

     

Better Bloc and Cubit Unit Testing

Similar to aprevious article, in this one I would like to address some pain points I've encountered while working with the bloc package, specifically related to testing.

As you might have seen in the official blocdocumentation, it promotes bloc_test package to facilitate writing unit tests.

After playing around with this package and the main testing facility it exposes, I couldn't write the kind of unit tests I was looking for.

So what's the deal 🤷🏽‍♂️

The problem is trying to assert that a certain state has being emitted independently of how current state has been built up or what its concrete value is.

blocTest does seem to have an option to skip a number of previously emitted values and to seed the current state and start from there. But this is a bit annoying in my opinion since you might not now how many emissions have been sent for a particular state to be reached
and using seed might be error prone and difficult to build the correct seed.

I love using thematchers package to do more flexible assertions, but to my surprise matchers didn't play around very nicely withblocTest function (I might be missing something, leave a comment if you know a workaround)

WithblocTest you have to explicitly say what previous states have been (or skip them).

blocTest('emits [-1] when CounterEvent.decrement is added',build:()=>counterBloc,act:(bloc)=>bloc.add(CounterEvent.decrement),expect:()=>[-1],// the history of all emitted states up until now :();
Enter fullscreen modeExit fullscreen mode

I tried using matchers in theexpect parameter but did not work.

Solution 🏆

Much like the solution proposed in the previous article aboutmobx testing but this time wrapped in custom testing method.

This a what you get:

cubitTest('Emits loaded state when registration service succeeds',build:()=>sut,stateField:(RegistrationStatestate)=>state.status,arrange:(){when(()=>mockRegistrationService.register(email:any(named:'email'),password:any(named:'password')),).thenAnswer((_)async{return;});sut.updateEmail('daniel@gmail.com');sut.updatePassword('12345678');},act:(RegistrationCubitcubit)=>cubit.register(),assertions:(MockCallable<Status>updatesStatusWith){verify(()=>updatesStatusWith(Status.loaded));});
Enter fullscreen modeExit fullscreen mode

This example can work perfectly with either:

verify(()=>updatesStatusWith(Status.loaded)),// ORverifyInOrder([()=>updatesStatusWith(Status.loading),()=>updatesStatusWith(Status.loaded),]);
Enter fullscreen modeExit fullscreen mode

This is exactly what I want, I can do an assertion on particular property and don't need to reason about what the previous states have to be and how to get there.

Note in this example I'm usingmocktail instead ofmockito but either could be used. Same goes for cubit or bloc

Brief explanation

Very similar toblocTest this also gives the same feel for arrange, act, assert structure but with some key differences, here is a short explanation of critical parameters:

  • stateField is interesting because it allows selecting a the particular state property you're interested in.

  • assertions is a closure with a single parameter of type MockCallable where T will be whatever you selected withstateField

Note that stateField could be the identity function in which case you could do assertions over the entire state

Also you might thinkbuild should return new instances of the bloc or cubit but I actually reuse the same instance for all tests so I can use the setup method and benefit from that as well.

And here is the implementation of the test wrapper:
github gist.

abstractclassCallable<T>{voidcall([T?arg]){}}classMockCallable<T>extendsMockimplementsCallable<T>{}@isTestvoidcubitTest<P,S,BextendsBlocBase<S>>(Stringdescription,{requiredBFunction()build,requiredFutureOrFunction(B)act,requiredPFunction(S)stateField,Function?arrange,Function(P)?inspect,requiredvoidFunction(MockCallable<P>updatesWith)assertions,})async{test(description,()async{finalexpectation=MockCallable<P>();arrange?.call();finalbloc=build();bloc.stream.listen((Sstate){finalfocusedProperty=stateField(state);inspect?.call(focusedProperty);expectation(focusedProperty);});awaitact(bloc);awaitbloc.close();assertions(expectation);});}
Enter fullscreen modeExit fullscreen mode

Hope you liked this and found it helpful in some way!

Until next time...

Top comments(0)

Subscribe
pic
Create template

Templates let you quickly answer FAQs or store snippets for re-use.

Dismiss

Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment'spermalink.

For further actions, you may consider blocking this person and/orreporting abuse

iOS & Flutter Developer
  • Location
    Medellin, Colombia
  • Joined

More fromDaniel Cardona Rojas

DEV Community

We're a place where coders share, stay up-to-date and grow their careers.

Log in Create account

[8]ページ先頭

©2009-2025 Movatter.jp