Generating Fake Data in Flutter using the Factory Pattern for Unit Testing
Tags: flutter, dart, faker, testing
Published: 2021-07-17 13:56:02

### Introduction

In this article we will learn how we can seamlessly generate fake data in Flutter that we can use with-in our test suites, or just for some placeholder data in our app when the backend is not completely setup yet or any other scenario that we just need a fake data in general. You will also learn how to create a Flutter package and use that package with-in your application, it can be used both in `lib` and `test` folder. And lastly you will learn how to setup a basic monorepo for your future projects, however I am not going to tackle about monorepos in this post on what are its pros and cons. 

However, if you don't really need the monorepo setup, just skip over to the actual implementation of the Factory Pattern.

### Packages

We will only require the following packages for us to seamlessly generate fake data.

- [faker](https://www.notion.so/Generating-Fake-Data-in-Flutter-using-the-Factory-Pattern-a420815e30e14b8b882c76b0b4410943): Will handle generating of random data of any data type.
- [uuid](https://www.notion.so/Generating-Fake-Data-in-Flutter-using-the-Factory-Pattern-a420815e30e14b8b882c76b0b4410943): Will generate a random uuid for us.
- [build_runner](https://pub.dev/packages/build_runner): Responsible for auto-generating us some boilerplate code.
- [freezed](https://pub.dev/packages/freezed): Is where we create our data models.
    - [freezed_annotation](https://pub.dev/packages/freezed_annotation)
    - [json_annotation](https://pub.dev/packages/json_annotation)
    - [json_serializable](https://pub.dev/packages/json_serializable)

### Setup

To follow along, we'll start to setup a monorepo, first create your terminal and navigate into that directory on where you put up all your projects. I prefer to have mine under `/dev/personal-projects` so I'll `cd` into it.

Next up is create a directory and call it "flutter-fake-data-factory-pattern" via `mkdir` terminal command.

```bash
mkdir flutter-fake-data-factory-pattern
```

Then next is to initialize Git in this directory so we can start up to track changes. Or maybe upload this into your repository or not. Whichever you prefer.

```bash
cd flutter-fake-data-factory-pattern && git init
```

Create a new directory called `packages` and create a Flutter package inside that directory and call it `app_core`

```bash
mkdir packages

cd packages

flutter create --template=package app_core
```

Once that is done you can proceed with installing the dependencies mentioned above.

Have the following in your dependencies `pubspec.yaml` file

```yaml
# Add this so our local package will not be published to pub.dev
publish_to: "none"

dependencies:
	freezed_annotation: ^0.14.2
	json_annotation: ^4.0.1
	faker: ^2.0.0
	uuid: ^3.0.4

dev_dependencies:
	build_runner: ^2.0.5
	freezed: ^0.14.2
	json_serializable: ^4.1.3
```

Install the dependencies afterwards.

```bash
cd app_core && flutter pub get
```

Next is to create a Flutter project is where our Flutter app lives. Now go up 2 directories via

```bash
cd ../..
```

Then create another directory and call it "apps", it is where we will house our Flutter apps.

```bash
mkdir apps

flutter create client_app
```

Then finally, just import the local package in "client_app" `pubspec.yaml` 

```yaml
app_core:
    path: ../../packages/app_core
```

Now that it is all setup and done we can proceed with the implementation of the Factory Pattern.

Keep in mind that since we have this kind of setup, all of our data models will be coming from the local package that we created `app_core`

### Creating the ModelFactory abstract

We'll want to create an abstract class for our factory classes so that they will be aligned with the actual implementation, then we'll just have a generic type argument to pass down to differ its results when other classes implements the abstract.

```dart
import 'package:faker/faker.dart';
import 'package:uuid/uuid.dart';

abstract class ModelFactory<T> {
  Faker get faker => Faker();

  /// Creates a fake uuid.
  String createFakeUuid() {
    return Uuid().v4();
  }

	/// Generate a single fake model.
  T generateFake();

  /// Generate fake list based on provided length.
  List<T> generateFakeList({required int length});
}
```

We are only keeping it simple, we just want a single item and a List of items of a particular data model.

- The getter `faker` is the `Faker` instance that got instantiated, it is where we grab all random data of any type.
- Next is the `createFakeUuid` method which is responsible for generating us a fake uuid out of the fly. Typically uuids are used when you have a data provider like Firestore or NoSQL databases like MongoDB, or a relational database that has a primary key type of uuid. But you can switch this up any however you want it to be.
- The `generateFake` is responsible for creating a single data model that requires implementation details; We'll have the implementation details for this in the factory class that extends this abstract class.
- Lastly, the `generateFakeList` will return a list, implementation details are not concerned here as well and it is being handled in the factory class the implements this abstract. It will just simply return a list of what is returned from `generateFake` method.

### Defining our data models

We'll just have it simple, we'll want two for now a `User` and a `Tweet` data model.

```dart
import 'package:freezed_annotation/freezed_annotation.dart';

part 'user.g.dart';
part 'user.freezed.dart';

@freezed
class User with _$User {
  factory User({
    required String id,
    required String name,
    required String email,
    int? age,
    @Default(false) bool suspended,
  }) = _User;

  factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
}
```

```dart
import 'package:freezed_annotation/freezed_annotation.dart';

part 'tweet.g.dart';
part 'tweet.freezed.dart';

@freezed
class Tweet with _$Tweet {
  factory Tweet({
    required String id,
    required String content,
    @Default([]) List<String> replies,
    @Default(0) int likes,
    @Default(0) int retweets,
  }) = _Tweet;

  factory Tweet.fromJson(Map<String, dynamic> json) => _$TweetFromJson(json);
}
```

Don't forget to run `build_runner` to let it generated the boilerplate code for us. Run it via this terminal command (You have to be in `app_core` directory otherwise this won't run):

```dart
flutter pub run build_runner build --delete-conflicting-outputs
```

If you want to learn more about data modeling with `freezed` package, read more about it from this [tutorial that I wrote](https://dev.to/carlomigueldy/data-modeling-with-flutter-using-freezed-package-4p69).

### Creating a factory class

Now to implement the abstract class `ModelFactory`

```dart
import 'package:app_core/app_core.dart';

class UserFactory extends ModelFactory<User> {
  @override
  User generateFake() {
    return User(
      id: createFakeUuid(),
      email: faker.internet.email(),
      name: '${faker.person.firstName()} ${faker.person.lastName()}'.trim(),
      age: faker.randomGenerator.integer(25),
      suspended: faker.randomGenerator.boolean(),
    );
  }

  @override
  List<User> generateFakeList({required int length}) {
    return List.generate(length, (index) => generateFake());
  }
}
```

```dart
import 'package:app_core/app_core.dart';

class TweetFactory extends ModelFactory<Tweet> {
  @override
  Tweet generateFake() {
    return Tweet(
      id: createFakeUuid(),
      content: faker.lorem.words(99).join(' '),
      likes: faker.randomGenerator.integer(5000),
      retweets: faker.randomGenerator.integer(2500),
      replies: List.generate(
        faker.randomGenerator.integer(105),
        (index) => faker.lorem.words(120).join(' '),
      ),
    );
  }

  @override
  List<Tweet> generateFakeList({required int length}) {
    return List.generate(length, (index) => generateFake());
  }
}
```

### Sample usage for generating fake data from the factory class

It's so easy and straight forward just like what you are thinking right now on how we can use these factory classes to generate us some fake data.

```dart
/// A new instance of [UserFactory]
final userFactory = UserFactory();

/// Generates a [User] data model
final fakeUser = userFactory.generateFake();

/// Will generate a [List] of 10 [User] data model
final fakeUsers = userFactory.generateFakeList(10);
```

We can just basically import it anywhere and we don't have to manually set up the values for each property a User data model has. 

### Conclusion

We learned how to use `Faker` package and applied abstraction to avoid writing up the same code again and again for other data models to generate fake data. This would come quite so handy when we write a lot of unit tests with mocking as we don't have to create a data model instance and define each properties for each test case. And lastly we can also make use of this in the UI for placeholder data when the API is not yet ready to integrate with the frontend app.

Full source code can be found from the repository

[https://github.com/carlomigueldy/flutter-fake-data-factory-pattern](https://github.com/carlomigueldy/flutter-fake-data-factory-pattern)