<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Peter Bryant</title>
    <description>The latest articles on DEV Community by Peter Bryant (@ptrbrynt).</description>
    <link>https://dev.to/ptrbrynt</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F64702%2Fa93357ea-d812-4ab2-a418-f64c06bdc769.png</url>
      <title>DEV Community: Peter Bryant</title>
      <link>https://dev.to/ptrbrynt</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ptrbrynt"/>
    <language>en</language>
    <item>
      <title>How to actually do TDD in Flutter</title>
      <dc:creator>Peter Bryant</dc:creator>
      <pubDate>Thu, 04 Aug 2022 08:59:46 +0000</pubDate>
      <link>https://dev.to/ptrbrynt/how-to-actually-do-tdd-in-flutter-4gj8</link>
      <guid>https://dev.to/ptrbrynt/how-to-actually-do-tdd-in-flutter-4gj8</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--cPwrnTT4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://images.unsplash.com/photo-1645351666032-1a86fd2affbc%3Fcrop%3Dentropy%26cs%3Dtinysrgb%26fit%3Dmax%26fm%3Djpg%26ixid%3DMnwxMTc3M3wwfDF8c2VhcmNofDExfHxmbHV0dGVyfGVufDB8fHx8MTY1OTYwMjU3OQ%26ixlib%3Drb-1.2.1%26q%3D80%26w%3D2000" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--cPwrnTT4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://images.unsplash.com/photo-1645351666032-1a86fd2affbc%3Fcrop%3Dentropy%26cs%3Dtinysrgb%26fit%3Dmax%26fm%3Djpg%26ixid%3DMnwxMTc3M3wwfDF8c2VhcmNofDExfHxmbHV0dGVyfGVufDB8fHx8MTY1OTYwMjU3OQ%26ixlib%3Drb-1.2.1%26q%3D80%26w%3D2000" alt="How to actually do TDD in Flutter" width="880" height="587"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This post is the result of a months-long existential crisis about how to do testing in Flutter apps. There are &lt;em&gt;so many&lt;/em&gt; resources, tutorials, and opinions on how this should be done, but most of them give what is, in my opinion, bad advice.&lt;/p&gt;

&lt;p&gt;The aim of this post is to put forward a case for a change in direction. The Flutter community is barrelling towards a nightmare of unmaintainable test suites, and development practices which waste more time than they save. We need to look at the practices we're encouraging as a community and ensure that we are promoting good engineering that delivers &lt;strong&gt;value&lt;/strong&gt; to our customers.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;A lot of the ideas in this post came from a fantastic talk by Ian Cooper at Devternity 2017 called "TDD: Where did it all go wrong?" It's worth watching that video first before trying to apply the ideas to Flutter. Here's the link: &lt;a href="https://www.youtube.com/watch?v=EZ05e7EMOLM"&gt;🚀 TDD, Where Did It All Go Wrong (Ian Cooper)&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The problems we're trying to solve
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Tests hold us hostage.&lt;/strong&gt; When we write over-specified tests, we couple them to our implementation details. This means that any changes we make to the implementation are likely to break a bunch of tests. This is extremely frustrating for a development team to have to deal with. It makes things difficult when they're supposed to be easy.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Achieving high coverage is hard.&lt;/strong&gt; When we test at class level - which most of the advice says we should - we can get fairly good test coverage without too much trouble. But the last 20% or so is usually extremely difficult to cover, and often gets left out. This means that there is loads of untested code in our project.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Writing tests takes too long.&lt;/strong&gt; We seem to spend most of our time writing fiddly tests to cover things that we know should just work.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Maintaining high coverage is hard.&lt;/strong&gt; The first three problems combined mean that over time, coverage slowly deteriorates as developers give up covering their changes with appropriate tests.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Some rules for TDD
&lt;/h2&gt;

&lt;p&gt;If we're going to solve the problems above and still have a well-tested app, we need to learn (or re-learn) some important principles. Some of these were totally new to me; some I knew but didn't understand properly.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;A unit is not a class.&lt;/strong&gt; Or a method, or a function. A &lt;em&gt;unit&lt;/em&gt; is an encapsulation of functionality, which could be implemented as lots of classes, or a single function, or an entire app. It's a super unhelpful word. I've stopped using the phrase "unit tests"; I call them "developer tests" or "automated tests" now.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test behaviour, not implementation.&lt;/strong&gt; You should write tests based on requirements like, "When I type in a valid email and password, then press the login button, I should be taken to the home screen." &lt;strong&gt;Don't&lt;/strong&gt; write tests like this: "When I type in a valid email address and password, then press the login button, the &lt;code&gt;login&lt;/code&gt; method on the &lt;code&gt;AuthRepository&lt;/code&gt; class should be called with the correct parameters."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;UI design is not behaviour.&lt;/strong&gt; It is totally impractical and unnecessary to test the design of your app using automated testing techniques such as golden testing. It holds your design hostage and means your tests break anytime you want to tweak something visual which doesn't actually change how the app works. The tooling is also horrible. This is precisely the purpose of human QA.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tests must not drive code design decisions.&lt;/strong&gt; I've seen some well-respected thought leaders in software engineering say that the design of your code should be informed or even dictated by your tests. But this breaks one of the fundamental rules of TDD, which is that tests should &lt;em&gt;not&lt;/em&gt; be coupled to the implementation of the behaviour under test. If your tests require you to design your code in a particular way, you've written your tests wrong.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Don't mock your own code.&lt;/strong&gt; I've often done this in order to isolate the class I'm testing from its dependencies. It's unnecessary, as we'll discover.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Don't skip the refactoring step.&lt;/strong&gt; The red-green-refactor cycle is much more important for TDD than most developers realise.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  An example
&lt;/h2&gt;

&lt;p&gt;Let's build a very simple password reset screen. It has a field for you to enter a username, which can't be blank, and a button which submits the request. If the request fails, we want to show the error message. Otherwise, we want to go back to the login screen.&lt;/p&gt;

&lt;p&gt;In this example, the app uses Firebase Auth. Because this is an &lt;strong&gt;external&lt;/strong&gt; dependency, we're going to use a test double which mimics the behaviour of the real &lt;code&gt;FirebaseAuth&lt;/code&gt; implementation. If you were using a different authentication system which worked over HTTP requests, you could use something like &lt;a href="https://pub.dev/packages/http_mock_adapter"&gt;&lt;code&gt;http_mock_adapter&lt;/code&gt;&lt;/a&gt; to fake your API responses.&lt;/p&gt;

&lt;p&gt;We're also using &lt;a href="https://riverpod.dev/"&gt;Riverpod&lt;/a&gt; as our DI solution. These techniques obviously don't require Riverpod, but I've found Riverpod to be the best DI/state management solution, so it's what I use.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Write a test (or set of tests) that fails
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'reset password screen'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;testWidgets&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="s"&gt;'''goes to the login screen when a valid username is entered and the request succeeds'''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tester&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;auth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;MockFirebaseAuth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;signedIn:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;tester&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;pumpWidget&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="n"&gt;ProviderScope&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nl"&gt;overrides:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
              &lt;span class="n"&gt;firebaseAuthProvider&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;overrideWithValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;MyApp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
              &lt;span class="nl"&gt;initialRoute:&lt;/span&gt; &lt;span class="s"&gt;'/login/forgot-password'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;),&lt;/span&gt;
          &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;tester&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;enterText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="n"&gt;find&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;byKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'forgot-password-username-field'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
          &lt;span class="s"&gt;'username'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;tester&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;tap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="n"&gt;find&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;byKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'forgot-password-submit-button'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;tester&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;pumpAndSettle&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="n"&gt;find&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;byType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LoginWidget&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
          &lt;span class="n"&gt;findsOneWidget&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="n"&gt;testWidgets&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="s"&gt;'''displays an error message in a snackbar when the user is not found'''&lt;/span&gt;
      &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tester&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;auth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;MockFirebaseAuth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="nl"&gt;signedIn:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nl"&gt;authExceptions:&lt;/span&gt; &lt;span class="n"&gt;AuthExceptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nl"&gt;sendPasswordResetEmail:&lt;/span&gt; &lt;span class="n"&gt;FirebaseAuthException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;code:&lt;/span&gt; &lt;span class="s"&gt;'auth/user-not-found'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;tester&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;pumpWidget&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="n"&gt;ProviderScope&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nl"&gt;overrides:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
              &lt;span class="n"&gt;firebaseAuthProvider&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;overrideWithValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;MyApp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
              &lt;span class="nl"&gt;initialRoute:&lt;/span&gt; &lt;span class="s"&gt;'/login/forgot-password'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;),&lt;/span&gt;
          &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;tester&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;enterText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="n"&gt;find&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;byKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'forgot-password-username-field'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
          &lt;span class="s"&gt;'username'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;tester&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;tap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="n"&gt;find&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;byKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'forgot-password-submit-button'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;tester&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;pumpAndSettle&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="n"&gt;find&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'User not found'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
          &lt;span class="n"&gt;findsOneWidget&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="n"&gt;testWidgets&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="s"&gt;'''shows a validation error when no username is entered'''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tester&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;tester&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;pumpWidget&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="n"&gt;ProviderScope&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;MyApp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
              &lt;span class="nl"&gt;initialRoute:&lt;/span&gt; &lt;span class="s"&gt;'/login/forgot-password'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;),&lt;/span&gt;
          &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;tester&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;tap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="n"&gt;find&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;byKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'forgot-password-submit-button'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;tester&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;pumpAndSettle&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="n"&gt;find&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;descendant&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nl"&gt;of:&lt;/span&gt; &lt;span class="n"&gt;find&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;byKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'forgot-password-username-field'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
            &lt;span class="nl"&gt;matching:&lt;/span&gt; &lt;span class="n"&gt;find&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Please enter your username'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
          &lt;span class="p"&gt;),&lt;/span&gt;
          &lt;span class="n"&gt;findsOneWidget&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Some important things to note about this test:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;We are running the whole app, rather than only pumping the widget under test. This is so we don't have to bother with any navigation mocking.&lt;/li&gt;
&lt;li&gt;We always refer to widgets by keys, not by types. This is to ensure we can change the type of widget without the test breaking (e.g. if an &lt;code&gt;ElevatedButton&lt;/code&gt; becomes a &lt;code&gt;TextButton&lt;/code&gt;).
– The exception to this rule is when we check that the &lt;code&gt;LoginWidget&lt;/code&gt; is visible. I think this is fine as the alternative would be wrangling to find the path of the current route or something.&lt;/li&gt;
&lt;li&gt;We're not using any mock-style verifications to check that the correct method was called on the &lt;code&gt;FirebaseAuth&lt;/code&gt; class. This would couple our test to the implementation details, which we must not do.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Run the tests and, of course, they all fail. Let's make them pass.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Make the test pass by making a mess
&lt;/h3&gt;

&lt;p&gt;Here, we just need to write as little code as possible to make sure the test passes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ForgotPasswordWidget&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;ConsumerStatefulWidget&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;ForgotPasswordWidget&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nd"&gt;@override&lt;/span&gt;
  &lt;span class="n"&gt;ConsumerState&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ForgotPasswordWidget&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;createState&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_ForgotPasswordWidgetState&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;_ForgotPasswordWidgetState&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;ConsumerState&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ForgotPasswordWidget&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;_formKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;GlobalKey&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;FormState&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;_usernameController&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TextEditingController&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="nd"&gt;@override&lt;/span&gt;
  &lt;span class="n"&gt;Widget&lt;/span&gt; &lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BuildContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Scaffold&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nl"&gt;body:&lt;/span&gt; &lt;span class="n"&gt;Form&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nl"&gt;key:&lt;/span&gt; &lt;span class="n"&gt;_formKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="nl"&gt;children:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="n"&gt;TextFormField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
              &lt;span class="nl"&gt;key:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'forgot-password-username-field'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
              &lt;span class="nl"&gt;controller:&lt;/span&gt; &lt;span class="n"&gt;_usernameController&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="nl"&gt;validator:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;!.&lt;/span&gt;&lt;span class="na"&gt;isEmpty&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;'Please enter your username'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
              &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;ElevatedButton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
              &lt;span class="nl"&gt;key:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'forgot-password-submit-button'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
              &lt;span class="nl"&gt;onPressed:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kd"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_formKey&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;currentState&lt;/span&gt;&lt;span class="o"&gt;!.&lt;/span&gt;&lt;span class="na"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;auth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ref&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;firebaseAuthProvider&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

                  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;sendPasswordResetEmail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                      &lt;span class="nl"&gt;email:&lt;/span&gt; &lt;span class="n"&gt;_emailController&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="p"&gt;);&lt;/span&gt;
                  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="kd"&gt;on&lt;/span&gt; &lt;span class="n"&gt;FirebaseAuthException&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;'auth/user-not-found'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                      &lt;span class="n"&gt;ScaffoldMessenger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;showSnackBar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                        &lt;span class="n"&gt;SnackBar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;content:&lt;/span&gt; &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'User not found'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
                      &lt;span class="p"&gt;);&lt;/span&gt;
                    &lt;span class="p"&gt;}&lt;/span&gt;
                  &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
              &lt;span class="p"&gt;},&lt;/span&gt;
              &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Submit'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;),&lt;/span&gt;
          &lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;In a real app, you would also need to ensure you've set your router up correctly.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;That's it! This code is sufficient to ensure all our tests pass. A couple things to note:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;If we ran this app, it would look terrible. We haven't applied any UI design to it yet. That's okay.&lt;/li&gt;
&lt;li&gt;Because it's a very simple example, the code looks okay. In a more complex widget, your code might be horrible and messy. That's okay too.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We can now move on to the final step.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Refactor, keeping the tests green all the way
&lt;/h3&gt;

&lt;p&gt;We now come to the most important and useful step of TDD. We have a basic implementation which we can prove is working. Now we can begin refactoring: the process of changing the design of our code and/or the UI &lt;strong&gt;without&lt;/strong&gt; changing the behaviour and causing the tests to fail.&lt;/p&gt;

&lt;p&gt;Each time we make a change, we can run the tests again to make sure we haven't broken something.&lt;/p&gt;

&lt;p&gt;In terms of refactoring, if you're not sure where you might go from here there is a great resource at &lt;a href="https://refactoring.guru/refactoring"&gt;refactoring.guru&lt;/a&gt; which helps you identify opportunities to improve the design of your code. You could extract methods or classes to separate concerns; you could improve variable naming; you could generalise something to make it reusable. The important thing to remember is that &lt;strong&gt;anything you do from here should not change the way the screen behaves, and your tests should pass after every refactoring step.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is also the moment at which you apply the UI design. Add fancy animations, loading states, pretty pictures, and all the rest. But don't change the functionality required by your tests.&lt;/p&gt;

&lt;p&gt;By the end, you should find that you have &lt;strong&gt;well-designed code that is 100% covered by tests.&lt;/strong&gt; If your coverage is below 100%, that &lt;em&gt;must&lt;/em&gt; mean that you've written some code that you didn't need to, or introduced new behaviour that isn't tested. &lt;strong&gt;Get rid of anything that you haven't written a test for, or write tests for the stuff you know you need.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Of course, any external dependencies you're mocking (like &lt;code&gt;FirebaseAuth&lt;/code&gt; in my example) won't be covered by tests. That's okay - if you're using Riverpod you can just exclude the providers from coverage reports:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="c1"&gt;// coverage:ignore-start&lt;/span&gt;
&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;firebaseAuthProvider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Provider&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;FirebaseAuth&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// coverage:ignore-end&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You've made a deliberate and good decision never to use this dependency in a test, so ignoring it in coverage reports is totally appropriate and justified.&lt;/p&gt;

&lt;h2&gt;
  
  
  The result
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;We have a suite of tests which protects the &lt;strong&gt;behaviour&lt;/strong&gt; of our code without locking in its implementation. We can re-implement parts of this code without breaking the tests. We will only need to change the tests if we're changing the behaviour (e.g. if we wanted to disable the Submit button until the user has entered a username).&lt;/li&gt;
&lt;li&gt;High code coverage is easy to achieve this way. By not mocking our own code, we cover huge swathes of it with very few tests.&lt;/li&gt;
&lt;li&gt;It was fast, and we got meaningful feedback at every step.&lt;/li&gt;
&lt;li&gt;The burden of maintaining these tests will be low, and it's easy to keep coverage high.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Adopting this approach allows you to use your tests as a tool rather than a shackle. They give you confidence when refactoring by ensuring nothing more than that the behaviour of your code stays consistent. Tests should &lt;em&gt;never&lt;/em&gt; hold you to any particular implementation or design, and our tests don't.&lt;/p&gt;

&lt;p&gt;The other important aspect of this approach is that it's astonishingly easy. You write fewer tests, and those tests are a much lower maintenance burden.&lt;/p&gt;

&lt;p&gt;All this means you can provide value to your users and customers faster, while still having a well-tested codebase. Truly the best of both worlds.&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>testing</category>
      <category>tdd</category>
    </item>
    <item>
      <title>Approaches to dependency injection in Flutter</title>
      <dc:creator>Peter Bryant</dc:creator>
      <pubDate>Fri, 06 May 2022 08:07:23 +0000</pubDate>
      <link>https://dev.to/ptrbrynt/approaches-to-dependency-injection-in-flutter-4311</link>
      <guid>https://dev.to/ptrbrynt/approaches-to-dependency-injection-in-flutter-4311</guid>
      <description>&lt;p&gt;Any object-oriented software project worth it's salt should adopt a dependency injection pattern. But when it comes to Flutter apps, there are far too many options. So what do all these options look like, and which is best?&lt;/p&gt;

&lt;h3&gt;
  
  
  Side-note: on state management
&lt;/h3&gt;

&lt;p&gt;A lot of the packages and techniques we're covering here cross over into the world of State Management. But dependency injection and state management are &lt;strong&gt;not&lt;/strong&gt; the same thing, and shouldn't be considered as such.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;State management&lt;/em&gt; is strictly to do with the state of your app's user interface. Should this button be enabled? What's the content of this text field? What page are we on right now?&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Dependency injection&lt;/em&gt; is concerned with providing each class within your app with the dependencies it needs to function e.g. a Bloc class depending on a Repository.&lt;/p&gt;

&lt;p&gt;As it happens, there are a lot of Flutter packages which advertise themselves as &lt;em&gt;both&lt;/em&gt; state management packages and dependency injection frameworks. They're not wrong, but this can sometimes cause some confusion when developers conflate the two patterns.&lt;/p&gt;

&lt;h2&gt;
  
  
  What are we trying to achieve?
&lt;/h2&gt;

&lt;p&gt;Dependency injection can be achieved without any special packages or techniques. Consider this class:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ClassA&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Some implementation...&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ClassB&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;objectA&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ClassA&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

  &lt;span class="c1"&gt;// Some implementation...&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, we have two classes: &lt;code&gt;ClassA&lt;/code&gt;, and &lt;code&gt;ClassB&lt;/code&gt;. &lt;code&gt;ClassB&lt;/code&gt; includes an instance of &lt;code&gt;ClassA&lt;/code&gt; as part of its implementation; in other words, &lt;code&gt;ClassB&lt;/code&gt; &lt;strong&gt;depends on&lt;/strong&gt; &lt;code&gt;ClassA&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;However, the code above has some problems.&lt;/p&gt;

&lt;p&gt;Let's say we change the implementation of &lt;code&gt;ClassA&lt;/code&gt; such that its constructor changes. We now have to go through our code and find every instance of &lt;code&gt;ClassA&lt;/code&gt; and change its constructor call. That's hugely tedious and will very quickly become impossible to maintain.&lt;/p&gt;

&lt;p&gt;Another issue is to do with testing. In our implementation above, &lt;code&gt;ClassB&lt;/code&gt; is deciding exactly what version of &lt;code&gt;ClassA&lt;/code&gt; it will use. So what about when we want to write unit tests for &lt;code&gt;ClassB&lt;/code&gt;? We will also find ourselves testing the implementation of &lt;code&gt;ClassA&lt;/code&gt;, which will add lots of complexity to our tests and ultimately make them less useful.&lt;/p&gt;

&lt;p&gt;We can solve these problems using the Dependency Injection pattern, which simply involves providing a class's dependencies via its constructor:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ClassB&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;ClassB&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;objectA&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;ClassA&lt;/span&gt; &lt;span class="n"&gt;objectA&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, we can pass whatever object we want as &lt;code&gt;objectA&lt;/code&gt;, including a mocked version of &lt;code&gt;ClassA&lt;/code&gt;. It also ensures that if the constructor for &lt;code&gt;ClassA&lt;/code&gt; changes, the implementation of &lt;code&gt;ClassB&lt;/code&gt; doesn't need to change too. This improves the separation of concerns and makes &lt;code&gt;ClassB&lt;/code&gt; independently testable.&lt;/p&gt;

&lt;p&gt;We can even take this one step further by extracting &lt;code&gt;ClassA&lt;/code&gt;'s interface into an abstract version:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ClassB&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;ClassB&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;objectA&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;InterfaceA&lt;/span&gt; &lt;span class="n"&gt;objectA&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This ensures that &lt;code&gt;ClassB&lt;/code&gt; is entirely independent of the underlying implementation of &lt;code&gt;InterfaceA&lt;/code&gt;, which further decreases coupling, and achieves &lt;a href="https://en.wikipedia.org/wiki/Dependency_inversion_principle"&gt;dependency inversion&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  We don't actually &lt;em&gt;need&lt;/em&gt; frameworks
&lt;/h2&gt;

&lt;p&gt;The obvious conclusion of the definition above is that dependency injection is astonishingly simple. So why are there so many frameworks and packages for it?&lt;/p&gt;

&lt;p&gt;The answer is largely about hiding complexity for developers. In large applications, the relationships between classes can become pretty complex. There are a few challenges in particular for Flutter apps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Environments – how can I make it easy to switch between staging/test dependencies and production ones?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Scopes – how can I control which dependencies should be available to which dependent classes? How do I know whether or not I'm missing a dependency in a given scope?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Lifecycles – how can I understand and control when dependencies should be created, recreated, or destroyed?&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A handful of frameworks and patterns exist, and each of them approaches the three concerns above in a slightly different way. I'm going to compare two extremely popular packages which represent the two most common approaches to dependency injection.&lt;/p&gt;

&lt;h2&gt;
  
  
  Provider
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://pub.dev/packages/provider"&gt;Provider&lt;/a&gt; is an extremely popular library/pattern in the Flutter world which takes advantage of Flutter's &lt;code&gt;InheritedWidget&lt;/code&gt; to provide dependencies via the widget tree.&lt;/p&gt;

&lt;p&gt;This makes it astonishingly simple to manage dependencies. All you have to do is wrap a widget with a &lt;code&gt;Provider&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="n"&gt;Provider&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ClassA&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;(&lt;/span&gt;
  &lt;span class="nl"&gt;create:&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ClassA&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt;
  &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;MyApp&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt;
&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, any child of the &lt;code&gt;MyApp&lt;/code&gt; widget can access the instance of &lt;code&gt;ClassA&lt;/code&gt; like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;watch&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ClassA&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Makes the widget listen to changes in `ClassA` and rebuild&lt;/span&gt;
&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;read&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ClassA&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Returns the instance of `ClassA` without listening to it&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So how well does Provider address the 3 concerns above?&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Environments: Provider doesn't have any built-in mechanism for environment management, which gives you the freedom to manage environment variables however you like. You could have different &lt;code&gt;main.dart&lt;/code&gt; files for each environment, or use Dart's &lt;code&gt;fromEnvironment&lt;/code&gt; methods to read variables passed in via the &lt;code&gt;flutter build&lt;/code&gt; and &lt;code&gt;flutter run&lt;/code&gt; commands. It's up to you!&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Scopes: You can scope your dependencies using Provider by only wrapping the widgets whose children need access to the given dependency. This makes a lot of sense for Flutter apps! A couple of disadvantages do exist though:&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Lifecycles: You get quite a lot of flexibility when it comes to deciding when objects are created by Providers. You can either let the Provider manage creation for you (and you get to decide whether it does lazy instantiation or not), or you can manage the creation of the dependency yourself and use a &lt;code&gt;Provider.value&lt;/code&gt; constructor to inject the object into the widget tree. This flexibility covers most requirements you might have when it comes to dependency lifecycle management.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;There are some other things to consider when using Provider:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;There is a limitation in that you can only have one Provider per type in your Widget tree. If you have more than one &lt;code&gt;Provider&amp;lt;ClassA&amp;gt;&lt;/code&gt;, then &lt;code&gt;context.read&lt;/code&gt; will just retrieve the closest one. This doesn't usually cause too many problems but is something to be aware of.&lt;/li&gt;
&lt;li&gt;Provider's error messaging is really helpful, which is a bonus when it comes to debugging&lt;/li&gt;
&lt;li&gt;Provider is the preferred dependency injection solution for the Bloc library, if you like to use that for state management&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Overall, I really like the flexibility and simplicity of Provider!&lt;/p&gt;

&lt;h2&gt;
  
  
  GetIt + Injectable
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://pub.dev/packages/get_it"&gt;GetIt&lt;/a&gt; describes itself as a service locator for Dart, and when paired with &lt;a href="https://pub.dev/packages/injectable"&gt;Injectable&lt;/a&gt; it becomes a full dependency injection framework, with lots of similarities to Dagger/Hilt from the Android world.&lt;/p&gt;

&lt;p&gt;Injectable relies on code generation to automatically write initialization code for GetIt. You can create an “injectable” class by simply annotating it with &lt;code&gt;@injectable&lt;/code&gt; (or &lt;code&gt;@singleton&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Instances of dependencies can then be accessed using this syntax: &lt;code&gt;GetIt.I&amp;lt;ClassA&amp;gt;()&lt;/code&gt;. Notice that there's no dependency on a &lt;code&gt;BuildContext&lt;/code&gt; here; unlike Provider, GetIt dependencies exist &lt;em&gt;outside&lt;/em&gt; the widget tree.&lt;/p&gt;

&lt;p&gt;How does it handle our three concerns?&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Environments: Injectable includes great support for managing environments. You can create a number of &lt;code&gt;Environment&lt;/code&gt; objects and use them to annotate your dependencies (e.g. &lt;code&gt;@staging&lt;/code&gt;, &lt;code&gt;@production&lt;/code&gt;). You then specify which environment you want when initializing GetIt. This is really intuitive!&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Scope: Scope is less easy to manage with GetIt. It's all manual so you would need to remember to reset your scopes when required according to events taking place in your app. This is easy to mess up and difficult to test.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Lifecycles: You do get some flexibility when it comes to lifecycles with GetIt/Injectable. You can choose whether dependencies are created fresh each time they're requested (a factory), or whether the same instance is passed on each request (a singleton). You can also choose whether dependencies are initialized lazily or not. You also have some control over when singletons are invalidated and need to be recreated. But the documentation around this isn't very comprehensive.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Overall, I'm not a huge fan of this approach for one reason: having global access to any dependency feels like a bit too much power to put in the hands of your team! It's far too easy to break conventions around the hierarchy of dependencies when you can just pull in whatever dependency you like from anywhere in your app. And while I'm not against code generation in principle, this doesn't feel like a problem that needs to be solved with more code generation.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Lots of other packages exist which are extremely similar to GetIt/Injectable, including Kiwi, Injector, Scope, and Stark. These are all different variations on the same idea of using a “container” to store and provide dependencies from outside the widget tree, and they all have the same advantages and drawbacks.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Packages we haven't talked about
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://pub.dev/packages/riverpod"&gt;Riverpod&lt;/a&gt;, which is &lt;strong&gt;not&lt;/strong&gt; a DI framework but is often mistaken for one.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://pub.dev/packages/bloc"&gt;Bloc&lt;/a&gt;. Again, not a DI framework but does include Provider-style dependency injection features. You can use Bloc without using Provider, but I think it works best with the Provider-style approach.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Which approach is better?
&lt;/h2&gt;

&lt;p&gt;Personally, having worked with GetIt/Injectable for a very long time and experimented with Provider more recently, I'm leaning very strongly towards the Provider-style approach to dependency management. It feels much more Flutter-native, and it's much more transparent in terms of how scopes and lifecycles are managed. That said, I can see it getting tedious when a project gets really big, and Injectable's code generation would certainly solve the complexity problem for larger projects.&lt;/p&gt;

&lt;p&gt;What do you think? Are there any frameworks or approaches I've missed? Do you agree that Provider is generally the better approach for Flutter apps?&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Why Bloc + Freezed is a match made in heaven</title>
      <dc:creator>Peter Bryant</dc:creator>
      <pubDate>Sat, 23 Apr 2022 08:06:25 +0000</pubDate>
      <link>https://dev.to/ptrbrynt/why-bloc-freezed-is-a-match-made-in-heaven-29ai</link>
      <guid>https://dev.to/ptrbrynt/why-bloc-freezed-is-a-match-made-in-heaven-29ai</guid>
      <description>&lt;p&gt;I'm a big fan of the &lt;a href="https://bloclibrary.dev"&gt;Bloc&lt;/a&gt; library for Flutter apps, and when coupled with the &lt;a href="https://pub.dev/packages/freezed"&gt;freezed&lt;/a&gt; package for creating immutable data classes and sealed unions, I think this pattern really shines.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sealed whats?
&lt;/h2&gt;

&lt;p&gt;Let's start with some definitions. In other languages (like &lt;a href="https://kotlinlang.org/docs/sealed-classes.html"&gt;Kotlin&lt;/a&gt;), there are patterns available such as union types, sealed classes, and pattern-matching.&lt;/p&gt;

&lt;p&gt;These are all slightly different expressions of the same idea: that you can create a class with a fixed set of subclasses, linking together multiple otherwise separate types under one “umbrella” class.&lt;/p&gt;

&lt;p&gt;Shapes are a good example of how this could be used. Here's an example in Kotlin:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Shape&lt;/span&gt;

&lt;span class="kd"&gt;data class&lt;/span&gt; &lt;span class="nc"&gt;Square&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;sideLength&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;Shape&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="kd"&gt;data class&lt;/span&gt; &lt;span class="nc"&gt;Rectangle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;Shape&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="kd"&gt;data class&lt;/span&gt; &lt;span class="nc"&gt;Circle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;Shape&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we are defining 3 classes – &lt;code&gt;Square&lt;/code&gt;, &lt;code&gt;Rectangle&lt;/code&gt;, and &lt;code&gt;Circle&lt;/code&gt;. Each has its distinct properties, but we're able to have them all extend the &lt;code&gt;Shape&lt;/code&gt; superclass.&lt;/p&gt;

&lt;p&gt;More importantly, because &lt;code&gt;Shape&lt;/code&gt; is a &lt;code&gt;sealed class&lt;/code&gt;, no other subtypes can be defined outside this file; in other words, we can restrict the subtypes of &lt;code&gt;Shape&lt;/code&gt; to the ones we define.&lt;/p&gt;

&lt;p&gt;So why would this be helpful when using the Bloc pattern?&lt;/p&gt;

&lt;p&gt;Let's consider a simple Counter bloc. It would have two Events: &lt;code&gt;CounterIncremented&lt;/code&gt; and &lt;code&gt;CounterDecremented&lt;/code&gt;. In pure Dart, we would have to do something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kd"&gt;abstract&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CounterEvent&lt;/span&gt; &lt;span class="o"&gt;{}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CounterIncremented&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;CounterEvent&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;CounterIncremented&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;incrementBy&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;incrementBy&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CounterDecremented&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;CounterEvent&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;CounterDecremented&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;decrementBy&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;decrementBy&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is fine, but there are a couple of noteworthy issues:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;For Blocs with lots of events, you can see how this approach would become very verbose and cumbersome.&lt;/li&gt;
&lt;li&gt;The above snippet doesn't take equality operators into account; that's more boilerplate we'd need to add.&lt;/li&gt;
&lt;li&gt;When we eventually implement this Bloc, our code will be full of type-casting and type-checking, which is easy to make a mess with.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If we were writing Kotlin, we could solve these problems by implementing &lt;code&gt;CounterEvent&lt;/code&gt; as a &lt;code&gt;sealed class&lt;/code&gt; with each event type as a data class extending the base class.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CounterEvent&lt;/span&gt;

&lt;span class="kd"&gt;data class&lt;/span&gt; &lt;span class="nc"&gt;CounterIncremented&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;incrementBy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;CounterEvent&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="kd"&gt;data class&lt;/span&gt; &lt;span class="nc"&gt;CounterDecremented&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;decrementBy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;CounterEvent&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Kotlin's &lt;code&gt;data class&lt;/code&gt; construct gives us immutability and equality without boilerplate.&lt;/p&gt;

&lt;p&gt;But we're not writing Kotlin, and Dart doesn't have support for sealed classes out-of-the-box. We need to find an alternative!&lt;/p&gt;

&lt;h2&gt;
  
  
  Freezed Events
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://pub.dev/packages/freezed"&gt;freezed&lt;/a&gt; package gives us a couple of important abilities: we can create immutable data classes, and we can create sealed unions. Perfect!&lt;/p&gt;

&lt;p&gt;Here's what our Counter events would look like using a freezed union:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:freezed_annotation/freezed_annotation.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;part&lt;/span&gt; &lt;span class="s"&gt;'counter_event.freezed.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="nd"&gt;@freezed&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CounterEvent&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;_$CounterEvent&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="kd"&gt;factory&lt;/span&gt; &lt;span class="n"&gt;CounterEvent&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;incremented&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;incrementBy&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;CounterIncremented&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="kd"&gt;factory&lt;/span&gt; &lt;span class="n"&gt;CounterEvent&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;decremented&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;decrementBy&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;CounterDecremented&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this implementation, freezed has given us two immutable event types with equality operators. It even generates bonus stuff like &lt;code&gt;copyWith&lt;/code&gt; and &lt;code&gt;toString&lt;/code&gt; methods. This is a totally battle-ready implementation of our &lt;code&gt;CounterEvent&lt;/code&gt; type.&lt;/p&gt;

&lt;p&gt;When it comes to actually using instances of &lt;code&gt;CounterEvent&lt;/code&gt;, freezed also gives us access to some &lt;a href="https://en.wikipedia.org/wiki/Pattern_matching"&gt;pattern-matching&lt;/a&gt; syntax. Here's how we could implement our event handler:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;when&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="nl"&gt;incremented:&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;incrementBy&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;emit&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;incrementBy&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
    &lt;span class="nl"&gt;decremented:&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;decrementBy&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;emit&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;decrementBy&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
&lt;span class="o"&gt;);&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This syntax is helpful for a couple of reasons. First, forces us to account for every possibility; in pure Dart with type-casting, we could easily forget about or ignore certain event types. Second, it's super easy to read and understand!&lt;/p&gt;

&lt;h2&gt;
  
  
  Freezed States
&lt;/h2&gt;

&lt;p&gt;Let's look at something more complex: writing a state type using freezed. Let's say we have some data to load: we'll probably want an initial state, an error state, and a success state. We also need a loading indicator to show the user when the data is being refreshed.&lt;/p&gt;

&lt;p&gt;Using freezed, we can implement a state class like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:freezed_annotation/freezed_annotation.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;part&lt;/span&gt; &lt;span class="s"&gt;'data_state.freezed.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="nd"&gt;@freezed&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DataState&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;_$DataState&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="kd"&gt;factory&lt;/span&gt; &lt;span class="n"&gt;DataState&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;initial&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;isLoading&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DataInitial&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="kd"&gt;factory&lt;/span&gt; &lt;span class="n"&gt;DataState&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;isLoading&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DataError&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="kd"&gt;factory&lt;/span&gt; &lt;span class="n"&gt;DataState&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;success&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Data&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;isLoading&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DataSuccess&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, we can implement a &lt;code&gt;BlocBuilder&lt;/code&gt; like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="n"&gt;BlocBuilder&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;DataBloc&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DataState&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;(&lt;/span&gt;
    &lt;span class="nl"&gt;builder:&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Column&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
            &lt;span class="nl"&gt;children:&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isLoading&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="n"&gt;_buildProgressIndicator&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt;
                &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;when&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                    &lt;span class="nl"&gt;initial:&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_buildInitial&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt;
                    &lt;span class="nl"&gt;error:&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_buildError&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
                    &lt;span class="nl"&gt;success:&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_buildSuccess&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
                &lt;span class="o"&gt;),&lt;/span&gt;
            &lt;span class="o"&gt;],&lt;/span&gt;
        &lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;},&lt;/span&gt;
&lt;span class="o"&gt;);&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are a couple of things to notice here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Because every state type has an &lt;code&gt;isLoading&lt;/code&gt; property, we can access it without knowing what subtype of state we have. Thanks, freezed!&lt;/li&gt;
&lt;li&gt;Again, the pattern-matching syntax provided by freezed is extremely useful here in giving us some super-readable code which forces us to handle all possible states.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Is this the perfect solution?
&lt;/h2&gt;

&lt;p&gt;No.&lt;/p&gt;

&lt;p&gt;There is no perfect solution! It could very easily be argued that freezed classes are still pretty verbose, and some developers prefer to not rely on code generation in their apps. And some folks just don't like Bloc at all for some reason. That's all fine – do what works for you!&lt;/p&gt;

&lt;p&gt;But, having worked on more than 10 new Flutter projects in the past year or so, I've found that this pattern allows us to build and iterate quickly on our code, and improves my team's ability to understand one another's work easily.&lt;/p&gt;

&lt;p&gt;As with lots of software engineering patterns and practices, there's no single right way to do things; the most important thing you can do is to pick one pattern and stick to it consistently.&lt;/p&gt;




&lt;p&gt;Liked this? &lt;a href="https://buymeacoffee/ptrbrynt"&gt;Buy me a coffee&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Find me elsewhere online &lt;a href="https://linktr.ee/ptrbrynt"&gt;here&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>dart</category>
      <category>bloc</category>
      <category>freezed</category>
    </item>
    <item>
      <title>My testing epiphany</title>
      <dc:creator>Peter Bryant</dc:creator>
      <pubDate>Wed, 11 Apr 2018 13:15:12 +0000</pubDate>
      <link>https://dev.to/ptrbrynt/my-testing-epiphany-4en2</link>
      <guid>https://dev.to/ptrbrynt/my-testing-epiphany-4en2</guid>
      <description>&lt;p&gt;I've been making apps for about 2 years, and in that time I never understood the community's obsession with unit testing. I figured that if I can run the app and see it working, what's the point in taking loads of time to write automated tests?&lt;/p&gt;

&lt;p&gt;The answer revealed itself to me recently.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You don't write tests to make sure your code works now: you write tests so that you can spot your code breaking in the future.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://i.giphy.com/media/3o8dFn5CXJlCV9ZEsg/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/3o8dFn5CXJlCV9ZEsg/giphy.gif" alt="Mind blown"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here's an example. We've written a file in &lt;a href="https://rotacloud.com/"&gt;RotaCloud&lt;/a&gt; for Android which contains a bunch of functions for working with time. Here's our method for formatting a timestamp - in seconds - as a 24-hour time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nc"&gt;Long&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;secondsToTime&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;SimpleDateFormat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"HH:mm"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Locale&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;UK&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This function is pretty important to our app: if it breaks, suddenly everyone's rotas will be displaying incorrect information.&lt;/p&gt;

&lt;p&gt;So what happens when a dev unwittingly changes this function like this?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nc"&gt;Long&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;secondsToTime&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;SimpleDateFormat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"HH:mm"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Locale&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;UK&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This change will not cause a crash in the app, but it will mean that the whole system is pretty useless, and our users will &lt;em&gt;not&lt;/em&gt; be happy.&lt;/p&gt;

&lt;p&gt;So we wrote a test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Test&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;secondsToTimeTest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;timeInSeconds&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1523451825&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;expectedTime&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"13:03"&lt;/span&gt;
    &lt;span class="nf"&gt;assertEquals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expectedTime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeInSeconds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;secondsToTime&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a really simple test, but running it before a new release ensures that this vital functionality keeps working.&lt;/p&gt;

&lt;p&gt;We can extend these tests to cover some other requirements too e.g. the hour should always be two digits: &lt;code&gt;09:30&lt;/code&gt; is correct, but &lt;code&gt;9:30&lt;/code&gt; isn't.&lt;/p&gt;

&lt;p&gt;So that was my testing epiphany. It's pretty easy to manually determine whether or not something works, just by looking at it. What automated tests do, however, is ensure that &lt;em&gt;all&lt;/em&gt; the app's functionality continues to work in the future, without you having to manually look through the whole thing.&lt;/p&gt;

&lt;p&gt;Testing is also incredibly useful for collaborative development: simply require everyone to run the test suite before submitting a pull request, and you'll find yourself having to deal with a lot fewer broken implementations.&lt;/p&gt;

</description>
      <category>testing</category>
    </item>
  </channel>
</rss>
