iTranslated by AI

The content below is an AI-generated translation. This is an experimental feature, and may contain errors. View original article
📂

[Flutter] Not Just for Testing! How to Use Mockito in the lib Directory

に公開

Introduction

In testing, you often use Mockito to create mocks and stubs for classes.

https://pub.dev/packages/mockito

However, I recently learned that Mockito can actually be used outside of test files, and I'd like to share how.

Target Audience

  • Those who want to use Mockito even under the lib directory
  • Those who want to build UIs using mock data on screen for now

Author's Environment at the Time of Writing

[✓] Flutter (Channel stable, 3.27.1, on macOS 15.1 24B2082 darwin-arm64, locale ja-JP)
[✓] Android toolchain - develop for Android devices (Android SDK version 35.0.0)
[✓] Xcode - develop for iOS and macOS (Xcode 16.1)
[✓] Chrome - develop for the web
[✓] Android Studio (version 2024.2)
[✓] VS Code (version 1.96.2)

User Model

class User {
  User({
    required this.name,
    required this.email,
    required this.password,
  });

  final String name;
  final String email;
  final String password;
}

Class or Package to Mock

This time, we'll use SharedPreferences as an example, so let's install it.

https://pub.dev/packages/shared_preferences

Installing Mockito

Don't forget this either.

https://pub.dev/packages/mockito

Creating Mocks

Create Mocks in lib

Just like in testing, we will create mocks in the file where we want to use them.
This time, we will mock the User model and SharedPreferences.

import 'package:flutter/material.dart';
import 'package:mockito/annotations.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:simple_base/domains/user.dart';

@GenerateNiceMocks([
  MockSpec<SharedPreferences>(),
  MockSpec<User>(),
])
class HomeScreen extends StatelessWidget {
  const HomeScreen({super.key});

  ...
}

Configure build.yaml for Automatic Generation

Running build_runner as is will not generate mocks.
So, create a build.yaml file directly under the root directory and enter the following:

targets:
  $default:
    builders:
      mockito|mockBuilder:
        generate_for:
          - test/**.dart
          - lib/**.dart

This enables automatic generation not only for the test directory but also for the lib directory.
Try running the automatic generation.

Set Stubs for Mocks

Just like creating stubs in tests, you can set stubs using when.

import 'package:flutter/material.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:simple_base/domains/user.dart';
import 'package:simple_base/main.mocks.dart';

@GenerateNiceMocks([
  MockSpec<SharedPreferences>(),
  MockSpec<User>(),
])
class HomeScreen extends StatelessWidget {
  const HomeScreen({super.key});

  @override
  Widget build(BuildContext context) {
    final user = MockUser();
    when(user.name).thenReturn('John Doe');
    when(user.email).thenReturn('john@example.jp');
    when(user.password).thenReturn('password');

    return Scaffold(
      body: SafeArea(
        child: Center(
          child: Column(
            spacing: 20,
            children: [
              Text(
                'Name: ${user.name} \nEmail: ${user.email} '
                '\nPassword: ${user.password}',
              ),
              ElevatedButton(
                onPressed: () {
                  final prefs = MockSharedPreferences();
                  when(prefs.getString('key')).thenReturn('Mock value obtained');
                  final value = prefs.getString('key');
                  if (value == null) return;

                  ScaffoldMessenger.of(context).showSnackBar(
                    SnackBar(content: Text(value)),
                  );
                },
                child: const Text('Tap Mock Method'),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

Precautions

While this approach might be acceptable for a mere sample project, for a release-oriented project, you should handle it with more caution.

First, I believe it's necessary to limit the files for which build.yaml allows automatic generation. This is to prevent accidental inclusion in production files.
For example, here's how to specify concrete files:

targets:
  $default:
    builders:
      mockito|mockBuilder:
        generate_for:
          - test/**.dart
          - lib/debug/debug_screen.dart # <= 💡 Specify files

And let's say, based on the example above, you limit it to debug_screen only.
This debug_screen should preferably be restricted to only be displayed in development environments using Flavor or similar mechanisms.

The separation of development environments is explained here:

https://zenn.dev/harx/articles/5e376198cd9b4a

Conclusion

Typically, Mockito is often used in testing environments,
but it simplifies the handling of mock data during debugging and development.
By effectively utilizing this method, you can proceed with development while handling mock data more flexibly.
Please give it a try!

Discussion