Writing Tests in Flutter {Part 1} : How to write Unit tests in Flutter
Tags: flutter, mobile, android, ios
Published: 2022-05-03 09:03:46

Any piece of software should function reliably. Reliability comes from having a deterministic outcome. One way to bring reliability to software development is by writing tests.

Tests not only help you catch bugs early in the development cycle, but makes sure that new code does not break old functionality. It forces you to write modular code.

In this tutorial, you will learn to write** unit tests for a flutter application.**

### Starter Project
Clone the starter project from [here](https://github.com/shounakmulay/flutter-testing). The repo is based on the Wednesday [Flutter Template](https://github.com/wednesday-solutions/flutter_template) but is trimmed down for this tutorial.

Once you clone the project, open it in VS Code or Android studio and checkout the **unit-test-start** branch.

You will see the following directory structure.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/geuhshdmlzcgp1vzddi7.png)

Build and run the application. You should see an app as shown below run on your simulator/emulator.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/epmujrqj73zkdhmti83z.png)

Play with the application to understand what it does. Search for any city in the search box and mark a few as favorites.

### What are unit tests?
A unit test is a test that verifies the behavior of a small and isolated piece of code. _Small_ and _Isolated _are 2 important parameters to consider while writing a unit test.

A unit test will target a single function. Any inputs to the function will be provided by you while writing the test. Any dependencies, such as other functions that are called internally, should be mocked.

Unit tests are quick to run and don’t require a lot of setup.

> Fun Fact: In a production application, the number of unit tests will be much higher than any other type of tests.
‍

What is testable code?
Before you proceed with writing any test cases, you need to make sure that the code you are writing is testable in the first place.

Consider the following example. We have a **Calculator class** with a function called **add**. The calculator class internally creates an object of the **CalculatorAPIService** to perform the calculations.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mz8i0cfxf8zf32n8r5s7.png)

To test the **add** function you will need to know how the **CalculatorAPIService** class works. This architecture pattern is bad for a number of reasons:

- Any failure in the **CalculatorAPIService** will fail your test.
- The **CalculatorAPIService** may have more dependencies of its own.
- Testing failure conditions may depend on **CalculatorAPIService.** 
- Testing for error states will become difficult.


**Dependency Injection** or **Inversion of Control** can be used to solve this problem. Instead of letting the **Calculator** class create its own dependencies, we provide it to the class via it’s constructor. This way you can control how the dependency behaves.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5mabsgxxzh8xp43ma0op.png)

> Note: Going over the concepts of Dependency Injection is out of scope of this tutorial. In this repo we are using get_it as the Dependency Injection solution and you can read more about it here.
‍

### Why mock?
Mocking means replacing the dependencies of the piece of code under test, with a fake or duplicate version that you have control over.

Mocking the dependencies of the subject under test will let you achieve predictable behaviour on every run of a test. With a mock, you can have a function return a particular value or throw an error whenever you want. Mocks also allow you to verify if a particular function was called a certain number of times. You will see all of this in practice when you write actual tests in the following sections.

With dependency injection in place, you can pass the mock versions of dependencies to the class under tests. In this tutorial you will use **mocktail** to mock the required dependencies. You can read more about **mocktail** [here](https://pub.dev/packages/mocktail).

### Anatomy of a unit test
Every unit test has a common structure. It can be broadly divided into 3 stages, the **pre test **stage, the **testing** stage, and the **post test** stage.

Pre Test: This is for initializing mocks, and the subject class being tested.
Test: This is where the test is executed.
Post Test: This is for resetting mock values.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0f9jpy04vlhul2ua13wg.png)

### Creating a test file
In flutter, all test files must end with **_test.dart** and must be put under the test directory.

You will see an empty **test** directory in the starter project. This is where we will write all the tests. You will write tests for **weather_repository_impl.dart**. It is located at **repository/weather**.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ocsso8jejgxmpfv0zmvj.png)

It is a good idea to mimic the directory structure of **lib** in the **test** folder as it makes it easier to locate relevant test files.

- Since **weather_repository_impl.dart **in located in **lib/repository/weather**, create a new file **weather_repository_impl_test.dart in test/repository/weather**.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/93bgaezp1f78qsvz36g5.png)

Add a **main** method. All test code should be inside this main method.
 

```
void main() {
  // Test code here!
}
```

‍

###Initial test setup
To test a function in the **weather_repository_impl.dart** we first need to create an instance of the repository. The **setUp** function is the perfect place to do it.

Add the **setUp** function to the test file you created and create an instance of **WeatherRepositoryImpl** here.

 

```
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_testing/repository/weather/weather_repository_impl.dart';

void main() {
  late WeatherRepositoryImpl weatherRepository;

  setUp(() {
    weatherRepository = WeatherRepositoryImpl();
  });
}
```

You will notice that the **WeatherRepositoryImpl** requires some other classes to be passed in its constructor. Since you are testing only the **WeatherRepositoryImpl** class here, you do not want any other classes to influence the result of a test case. This is where mocking plays an important role. We will provide all the dependencies as mocks so that we can control their behaviour as required.

Open **pubspec.yaml** and add the mocktail dependency to the **dev_dependencies** section.
 
> dev_dependencies:  mocktail: ^0.3.0    # other dependencies

Run **flutter pub get** after adding the dependency.

With the dependency added let’s start adding the mocks. Since mocks reused in multiple test files, it’s better to extract them to a separate folder. Create a new director under **test** called **mocks** and add a file called **mocks.dart** to that directory.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m4f1i5x910ddpb7oxars.png)

The **WeatherRepositoryImpl** class has a couple of services, a few mappers and a repository as its dependencies.

> Note:-
> -Services are classes that give access to data sources
> -Mappers are classes that convert service data classes to domain data classes.

Create** mocks** for all the dependencies. To create a mock just extends the Mock class. Here we will also implement the interface that is used by the original class so that it is identified as a valid object. Add the following to the **mocks.dart** file.
 

```
import 'package:flutter_testing/repository/date/date_repository.dart';
import 'package:flutter_testing/repository/weather/domain_city_mapper.dart';
import 'package:flutter_testing/repository/weather/local_city_mapper.dart';
import 'package:flutter_testing/repository/weather/local_day_weather_mapper.dart';
import 'package:flutter_testing/repository/weather/local_weather_mapper.dart';
import 'package:flutter_testing/services/weather/local/weather_local_service.dart';
import 'package:flutter_testing/services/weather/remote/weather_remote_service.dart';
import 'package:mocktail/mocktail.dart';

// Service
class MockWeatherLocalService extends Mock implements WeatherLocalService {}

class MockWeatherRemoteService extends Mock implements WeatherRemoteService {}

// Mappers
class MockDomainCityMapper extends Mock implements DomainCityMapper {}

class MockLocalCityMapper extends Mock implements LocalCityMapper {}

class MockLocalWeatherMapper extends Mock implements LocalWeatherMapper {}

class MockLocalDayWeatherMapper extends Mock implements LocalDayWeatherMapper {}

// Repositories
class MockDateRepository extends Mock implements DateRepository {}

```
‍

With the mocks declared, you can now go back to **weather_repository_impl_test.dart** and use the mocks as dependencies to the **WeatherRepositoryImpl** class as shown below.

 

```
import 'package:flutter_testing/repository/date/date_repository.dart';
import 'package:flutter_testing/repository/weather/domain_city_mapper.dart';
import 'package:flutter_testing/repository/weather/local_city_mapper.dart';
import 'package:flutter_testing/repository/weather/local_day_weather_mapper.dart';
import 'package:flutter_testing/repository/weather/local_weather_mapper.dart';
import 'package:flutter_testing/repository/weather/weather_repository.dart';
import 'package:flutter_testing/repository/weather/weather_repository_impl.dart';
import 'package:flutter_testing/services/weather/local/weather_local_service.dart';
import 'package:flutter_testing/services/weather/remote/weather_remote_service.dart';
import '../../mocks/mocks.dart';

void main() {
  late WeatherLocalService weatherLocalService;
  late WeatherRemoteService weatherRemoteService;
  late DomainCityMapper domainCityMapper;
  late LocalCityMapper localCityMapper;
  late LocalWeatherMapper localWeatherMapper;
  late LocalDayWeatherMapper localDayWeatherMapper;
  late DateRepository dateRepository;

  late WeatherRepository weatherRepository;

  setUp(() {
    weatherLocalService = MockWeatherLocalService();
    weatherRemoteService = MockWeatherRemoteService();
    domainCityMapper = MockDomainCityMapper();
    localCityMapper = MockLocalCityMapper();
    localWeatherMapper = MockLocalWeatherMapper();
    localDayWeatherMapper = MockLocalDayWeatherMapper();
    dateRepository = MockDateRepository();

    weatherRepository = WeatherRepositoryImpl(
      weatherLocalService: weatherLocalService,
      weatherRemoteService: weatherRemoteService,
      domainCityMapper: domainCityMapper,
      localCityMapper: localCityMapper,
      localWeatherMapper: localWeatherMapper,
      localDayWeatherMapper: localDayWeatherMapper,
      dateRepository: dateRepository,
    );
  });
}
```

Next add the **tearDown** function to reset state.

 

```
void main() {
	...
	
	tearDown(() {
    resetMocktailState();
  });
}

```
‍

That was a lot of setup steps. Fortunately, you won’t have to do this every time as some of this setup (like the mocks) can be shared between multiple test files.

You are now ready to write your first test.

###Writing a unit test
The first step to writing a unit test is to pick the unit of code that you want to test. For your first test, let’s pick the **setCityAsFavorite** function from the very bottom of **WeatherRepositoryImpl**.


```
@override
  Future setCityAsFavorite(City city) async {
    await weatherLocalService.markCityAsFavorite(
      city: localCityMapper.map(city),
    );
  }

```
‍

Once you choose the unit of code, identify the steps involved in it. Doing so helps determine what parts to mock and what parts to verify in a test. For e.g.

- **setCityAsFavorite** accepts a **City** as input.
- **City** is converted to **LocalCity** by calling **localCityMapper.map** function.
- markCityAsFavorite is called on the weatherLocalService with the result of the map function.

Let’s now write the **test** function. This will run every time you call flutter test. It accepts 2 parameters, the name of the test and the actual test code itself. You will use the **Given, When, Then** pattern to describe a test. It breaks a test into three parts:

- **Given** some context
- **When** some action is carried out
- **Then** a particular set of action should occur
.
Write a new test as shown below. You will name it in the **Given, When, Then ** pattern.

```
test("Given a valid City object, When setCityAsFavorite is called and no error occurs, Then markCityAsFavorite is called with LocalCity object", () {
  // Given

  // When

  // Then
});
```

The **weatherLocalService** is a mock object, calling any functions in it will not return any values. You need to tell the mocking library what value should be returned when a particular function is called with a certain set of arguments. To do this you will also create some data objects with dummy data. For the current test function we need the the **City** and the **LocalCityCompanion** data objects.

 

```
test("Given a valid City object, When setCityAsFavorite is called and no error occurs, Then markCityAsFavorite is called with LocalCity object", () {
    // Given
    final testCity = City(
      id: 1,
      title: "title",
      locationType: "locationType",
      location: "location",
    );
    const testLocalCity = LocalCityCompanion(
      woeid: Value(1),
      title: Value("title"),
      locationType: Value("locationType"),
      location: Value("location"),
    );

    // When

    // Then
  });
```

The mocking library needs to be told that when **localCityMapper.map** is called with **testCity**, it should return the **testLocalCity**. For this use the **when** function from **mocktail**.

```
test(
      "Given a valid City object, When setCityAsFavorite is called and no error occurs, Then markCityAsFavorite is called with LocalCity object",
      () {
    // Given
    final testCity = City(...);
    const testLocalCity = LocalCityCompanion(...);
    when(() => localCityMapper.map(testCity)).thenReturn(testLocalCity);
        when(() => weatherLocalService.markCityAsFavorite(city: testLocalCity)).thenAnswer((_) async {});

    // When

    // Then
  });
```

With the mock setup done, you need to call the actual function under test. Call the **setCityAsFavorite** function, with **testCity** as the argument. Also since **setCityAsFavorite** is async, mark the test function as **async** as well.

```
test(
      "Given a valid City object, When setCityAsFavorite is called and no error occurs, Then markCityAsFavorite is called with LocalCity object",
      () async {
    // Given
    ...
        ...

    // When
    await weatherRepository.setCityAsFavorite(testCity);

    // Then
  });

```
‍

Final step in the test is to verify that the expected function calls were made and the expected data was returned. Since the function you are testing here does not return anything, we will look at how to check for that in a later test case. For now, let’s verify that both the **markCityAsFavorite** and the **map** function was called once with the **verify** function.

```
test(
      "Given a valid City object, When setCityAsFavorite is called and no error occurs, Then markCityAsFavorite is called with LocalCity object",
      () async {
    // Given
    ...
        ...

    // When
    ...
        ...

    // Then
    verify(() => localCityMapper.map(testCity)).called(1);
    verify(() => weatherLocalService.markCityAsFavorite(city: testLocalCity)).called(1);

		// 1
    verifyNoMoreInteractions(localCityMapper);
    verifyNoMoreInteractions(weatherLocalService);
    verifyZeroInteractions(weatherRemoteService);
  });
```

- The **verifyNoMoreInteractions** function checks that no more function calls happen on the given mock.

- The **verifyZeroInteractions** checks that no function was ever called on the given mock for the entire duration of the test.

You can now run the test by pressing the green button next to the test function or by running **flutter test**.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/svw2wukgk8xmq2t8nlbw.png)

The test should pass. You should see a similar output in the console.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ejuro1bc22oh53lyk6yn.png)

### Expecting results in unit test
Create a new test for the **getFavoriteCitiesList** function. The structure will be the same as before: creating test data, mocking function return values, calling the function under test, verifying expected function calls.

```
test(
      "Given local service returns list of LocalCityData, When getFavoriteCitiesList is called, Then Future> is returned",
      () async {
    // Given
    final localCityData = [
      LocalCityData(
        woeid: 1,
        title: "title",
        locationType: "locationType",
        location: "location",
      )
    ];
    final cityData = [
      City(
        id: 1,
        title: "title",
        locationType: "locationType",
        location: "location",
      )
    ];
    when(() => weatherLocalService.getFavouriteCities())
        .thenAnswer((_) => Future.value(localCityData));
    when(() => domainCityMapper.mapList(localCityData)).thenReturn(cityData);

    // When
    final result = await weatherRepository.getFavoriteCitiesList();

    // Then
    verify(() => weatherLocalService.getFavouriteCities()).called(1);
    verify(() => domainCityMapper.mapList(localCityData)).called(1);
  });

```
The current function under test returns a result. It is important to check that the return value is as expected. To do that we will use the **expect** function. The expect function can check a range of values, you can read more about it [here](https://api.flutter.dev/flutter/flutter_test/expect.html).

Add the following **expect** functions to check if the returned results is valid. In the first **expect** we are checking the length of the list returned and in the second expect we are checking the entire result itself.

 

```
test(
      "Given local service returns list of LocalCityData, When getFavoriteCitiesList is called, Then Future> is returned",
      () async {
    // Given
    ...

    // When
    ...

    // Then
		...
    expect(result.length, localCityData.length);
    expect(result, cityData);
  });
```

Running this test should give the following result


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/58gr5s24coidlzac4tem.png)

### Expecting exceptions
In some situations, you may want to check that if a particular function throws an exception. Create one more test for **setCityAsFavorite**. You will test the condition where if the **localCityMapper** throws an exception, that exception is surfaced by **setCityAsFavorite**. For this you will instruct the mock to throw an exception instead of returning a value using the **thenThrow** method.

```
test(
      "Given setCityAsFavorite is called with a valid City object, When localCityMapper throws an exception, Then the same exception is surfaced to the caller",
          () async {
        // Given
        final testCity = City(
          id: 1,
          title: "title",
          locationType: "locationType",
          location: "location",
        );
        final testException = Exception("Test exception");
        when(() => localCityMapper.map(testCity)).thenThrow(testException);

        // When


        // Then

      });
```

To test if a function throws an exception, you need to combine the **when** and **then** steps into a single expect block.

```
test(
      "Given setCityAsFavorite is called with a valid City object, When localCityMapper throws an exception, Then the same exception is surfaced to the caller",
      () async {
    // Given
    ...
        ...

    expect(
      // When
      () async => await weatherRepository.setCityAsFavorite(testCity),
      // Then
      throwsA(same(testException)),
    );
  });

```
That’s it! You are now capable of writing unit tests for any situation.

As you might have noticed, many tests require the same dummy data. You can extract these dummy test data objects into a separate file for easy re-use.

###Where to go from here?
To get the complete code from this tutorial, checkout the **unit-test-end** branch of the repo or view it on [GitHub](https://github.com/shounakmulay/flutter-testing/tree/unit-test-end).

You can also check out the [Wednesday Flutter Template](https://github.com/wednesday-solutions/flutter_template) that this repo is based on.

In the Second part of this series, we will look at testing flutter widgets.

Hope you enjoyed reading this article and if you have your own learnings to share please feel free to do by tweeting at us [here](https://twitter.com/wednesdaysol/status/1520017501546958850?s=20&t=jlnXTTSEf-UEx67qHf4DpA)!

‍Originally appeared on: https://www.wednesday.is/writing-tutorials/tests-in-flutter-part-1-how-to-write-unit-tests-in-flutter

#### About the Author
A master of Android and Flutter apps, Shounak is busy leading teams of mobile developers by day. A gaming enthusiast,  you can always find him in front of a screen looking for a good movie to watch. An avid photographer in his free time Shounak tries to capture different landscapes at sunset.