Blog-Image
  • Codeaamy
  • March 9, 2024

  • 0 Comments

Unit Test the Change Notifier in Flutter

This is the Third article in the “Master Testing In Flutter” series. The Previous article was about Unit Testing the Http function to get all the quotes from the API. If you have not read the previous blogs, I would advise you to read them before you continue with the Unit Test the Change Notifier. Here is the list of other articles in this series, in case you want to read it.

Let’s start with Unit Testing the Change Notifier. As we previously discussed in Unite Test we test small functions or classes. Before starting with testing let us first explore the todo_notifier . Navigate to todo_notifier.dart

class TodoNotifier extends ChangeNotifier {
  final TodoService _todoService;

  TodoNotifier(this._todoService);

  List<Todo> _todos = [];
  List<Todo> get todos => _todos;

  bool _isLoading = false;
  bool get isLoading => _isLoading;

  void setLoader(bool value) {
    _isLoading = value;
    notifyListeners();
  }

  Future getTodos() async {
    setLoader(true);
    _todos = await _todoService.getTodos();
    setLoader(false);
  }
}

final _client = Client();
final todosNotifierProvider =
    ChangeNotifierProvider((ref) => TodoNotifier(TodoService(_client)));
  • This code defines a TodoNotifier class that extends ChangeNotifier, a class provided by the Flutter framework that can notify its listeners when its state changes.
  • The TodoNotifier class has a _todoService property that represents an instance of a TodoService class, which communicates with a backend API to fetch todo items.
  • The _todos property is a list of Todo objects, and the getTodos method fetches the todos from the API by calling _todoService.getTodos(). The setLoader method is used to toggle the _isLoading boolean property, which represents whether the todos are currently being fetched or not.
  • When the setLoader method is called, it sets the _isLoading property to the passed value and notifies its listeners of the state change by calling notifyListeners().
  • The isLoading property returns the current value of _isLoading. The todos property returns the current value of _todos.
  • Finally, the code defines a todosNotifierProvider variable that creates a ChangeNotifierProvider instance. The ChangeNotifierProvider is a widget that provides the TodoNotifier instance to its child widgets via the Provider package. The TodoNotifier instance is constructed with an instance of TodoService, which is constructed with a Client instance.

Test Change Notifier

Create a new file todo_change_notifier_test.dart in a test folder. This is where we will write the tests to test the change notifier. Add the following test code in the main function.

void main() {
  late TodoNotifier sut_todoNotifier;

  setUp(() {
    sut_todoNotifier = TodoNotifier();
  });
}

The above code declares a variable named sut_todoNotifier of type TodoNotifier as late, which means that it is uninitialized but will be assigned a value before it is used.

The prefix sut is commonly used as a naming convention in software testing to indicate that this is a System Under Test (SUT) object, which is the object being tested.

The setUp() function is used to set up the test environment before running each test case. In this case, it initializes the sut_todoNotifier variable by creating a new instance of the TodoNotifier class.

sut_todoNotifier = TodoNotifier() throws an error as it requires TodoService at the time of initialisation.

We have to use a mock object like MockTodoService which is useful in testing scenarios where the real TodoService object is difficult to use, has unpredictable behaviour, or is unavailable. By using a mock object, developers can isolate the behaviour of the TodoService and test the behaviour of other parts of the system without worrying about the state of the TodoService itself.

Mocking Data with Mocktail

Add the following code on top of

class MockTodoService extends Mock implements TodoService {}

Note: — You will need to add mocktail package in the pubspec.yaml

This code defines a mock class called MockTodoService that extends the Mock class and implements the TodoService interface. The Mock class is part of a testing mocktail that allows developers to create mock objects that simulate the behaviour of real objects for testing purposes.

By extending Mock, the MockTodoService class inherits methods that can be used to set up expectations and define behaviour for the mock object. These expectations and behaviours can be used to test how other parts of the system interact with the TodoService object.

Pass the MockTodoService variable inside the constructor of sut_todoNotifier();

  late TodoNotifier sut_todoNotifier;
  late MockTodoService mockTodoService;

  setUp(() {
    mockTodoService = MockTodoService();
    sut_todoNotifier = TodoNotifier(mockTodoService);
  });

Initial Testing

First, we have to test the Initial values when the change notifier initialises

  test('Should check initial values are correct', () {
    expect(sut_todoNotifier.isLoading, false);
    expect(sut_todoNotifier.todos, []);
  });

The first assertion checks that the isLoading property is initially set to false. This means that when the TodoNotifier object is created, it should not be in a loading state.

The second assertion checks that the todos property is initially set to an empty list. This means that when the TodoNotifier object is created, it should not contain any todos.

  void getTodoServiceReturnsQuotes() {
    when(() => mockTodoService.getTodos())
        .thenAnswer((_) async => mockTodosForTesting);
  }

This code is defining a mock behaviour for a TodoService instance using the mockTodoService object. Specifically, it is defining the behaviour of the getTodos() method of the TodoService.

The when() function is a part of the mocktail library, and it is used to define the behaviour of a mocked object when a specific method is called on it. In this case, when() is called with an anonymous function that returns the result of calling getTodos() on the mockTodoService object.

The thenAnswer() method is called on the result of the when() function, and it defines the behaviour that should be returned when the method is called. In this case, it is returning a Future that resolves to mockTodosForTesting, which is a predefined list of Todo objects that are being used for testing purposes. It is located in key_constants.dart file

Overall, this code is defining a mock behaviour for a TodoService instance that always returns a predefined list of Todo objects when its getTodos() method is called. This is useful for testing code that relies on the behaviour of the TodoService without actually having to interact with a real instance of the service.

Testing the getTodo function

Now let’s test the getTodo function from our Change Notifier. Add the following test code.

  test('''Loading data indicator, 
      sets Todos, indicates data is not loaded anymore''', () async {
    getTodoServiceReturnsQuotes();
    final future = sut_todoNotifier.getTodos();
    expect(sut_todoNotifier.isLoading, true);
    await future;
    expect(sut_todoNotifier.todos, mockTodosForTesting);
    expect(sut_todoNotifier.isLoading, false);
  });

The test uses the getTodoServiceReturnsQuotes() function to simulate the loading of Todos, which likely retrieves the Todos from a service or data source. Then, the test calls the getTodos() method and verifies that the loading state of the sut_todoNotifier instance is set to true.

After that, the test awaits the completion of the future variable, which should represent the loading of the Todos. Once the loading is complete, the test verifies that the Todos have been correctly loaded by checking that the todos property of sut_todoNotifier contains the expected values.

Finally, the test checks that the loading state of the sut_todoNotifier instance is set to false after the Todos have been loaded.

Grouping All The Tests

Now we have successfully written all the tests for our change notifier, you can run each test and check. Also, you will notice that we have to run each test separately which is a tedious task.

Let’s group all the tests so that we can run them all together.

group('Group of Tests for testing change notifier', () {});

Just as we have test function to write the tests, we have a group function, which groups all the tests written. Just add the written tests inside the callback function of the group test.

Finally todo_change_notifier_test.dart should look like this.


class MockTodoService extends Mock implements TodoService {}

void main() {
  late TodoNotifier sut_todoNotifier;
  late MockTodoService mockTodoService;

  setUp(() {
    mockTodoService = MockTodoService();
    sut_todoNotifier = TodoNotifier(mockTodoService);
  });

  group('Group of Tests for testing change notifier', () {
    test('Should check initial values are correct', () {
      expect(sut_todoNotifier.isLoading, false);
      expect(sut_todoNotifier.todos, []);
    });

    void getTodoServiceReturnsQuotes() {
      when(() => mockTodoService.getTodos())
          .thenAnswer((_) async => mockTodosForTesting);
    }

    test('''Loading data indicator, 
      sets Todos, indicates data is not loaded anymore''', () async {
      getTodoServiceReturnsQuotes();
      final future = sut_todoNotifier.getTodos();
      expect(sut_todoNotifier.isLoading, true);
      await future;
      expect(sut_todoNotifier.todos, mockTodosForTesting);
      expect(sut_todoNotifier.isLoading, false);
    });
  });
}

Here I conclude the third article from Master Testing in Flutter series. In this article, I explained how you can Test the Change Notifier and mock data with the Mocktail package.

This also concludes the Unit Testing part of our Testing Series. In the next article. I will cover Widget Testing, and concepts of testing a widget.

If you want to check out the rest of the blogs from the testing Series here is a list of all the blogs present. I would still address if you follow this guide step by step.

Thank You for reading my blog, if you have any doubts or recommendations please let me know in the comment section below. I would like to know what are the other way to globally listen to network connectivity.

You have 50 Claps per day. Claps do Motivate us to keep writing. Gotta Use Them All

Tags

Leave A Comment