Title: TypeError in Flutter Test: Null is not a subtype of Future

I’m writing a Flutter test where I’m trying to mock a method that calculates the BMI. Despite using when(…).thenReturn(…) to return a future value, I’m encountering the following error:

══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK ╞════════════════════════════════════════════════════ The following _TypeError was thrown running a test: type 'Null' is not a subtype of type 'Future<String>'

How It Works

  1. User Interaction:

    • The user opens the app and enters their weight and height in the provided text fields.
    • The user presses the “Calculate BMI” button.
  2. Frontend Processing:

    • The app sends a request to the backend API with the weight and height as parameters.
    • The app uses the Provider package to manage state and the ApiService to handle the HTTP request.
  3. Backend Processing:

    • The Spring Boot application receives the request and processes the BMI calculation.
    • The result is sent back to the app as a response.
  4. Result Display:

    • The app displays the BMI result and changes the text color based on the BMI category.

Here is relevant part of my code:

widget_test.dart

// Define a mock class for ApiService using Mockito
class MockApiService extends Mock implements ApiService {}
void main() {
  testWidgets('BMI Calculator Test', (WidgetTester tester) async {
    final MockApiService mockApiService = MockApiService();

    // Mock response for calculateBMI method
    when(mockApiService.calculateBMI(88, 186)).thenReturn(Future.value('22.2'));

    await tester.pumpWidget(
      ChangeNotifierProvider(
        create: (context) => mockApiService,
        child: MaterialApp(
          home: BmiCalculatorApp(),
        ),
      ),
    );

    // Verify if 'Calculate BMI' button is found on the screen
    expect(find.text('Calculate BMI'), findsOneWidget);

    // Find the ElevatedButton widget with 'Calculate BMI' text and tap it
    await tester.tap(find.widgetWithText(ElevatedButton, 'Calculate BMI'));
    await tester.pump();

    // Wait for the async operation to complete
    await tester.pump(Duration(seconds: 1));

    // Verify if 'BMI result:' text is found exactly once on the screen
    expect(find.text('Your BMI is 22.2'), findsOneWidget);
  });
}

api_service.dart

import 'package:mockito/mockito.dart';
import 'package:flutter_test/flutter_test.dart';

// Define a service class
class ApiService {
  Future<String> calculateBMI(int weight, int height) async {
    // Implementation that calculates BMI
    return ((weight / (height / 100 * height / 100)).toStringAsFixed(1));
  }
}

// Define a mock class
class MockApiService extends Mock implements ApiService {}

void main() {
  // Create an instance of the mock class
  final mockApiService = MockApiService();

  // Set up the mock to return a specific value
  when(mockApiService.calculateBMI(88, 186)).thenReturn(Future.value('22.2'));

  // Test case
  test('Calculate BMI', () async {
    final result = await mockApiService.calculateBMI(88, 186);
    expect(result, '22.2');
  });
}

main.dart

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'api_service.dart'; // Import the ApiService class

void main() {
  runApp(
    // This is the entry point of my Flutter application.
    ChangeNotifierProvider( // This widget is used to provide a ChangeNotifier to its descendants. ChangeNotifierProvider: Listens for changes in the information and updates the app automatically.
      create: (context) => ApiService(baseUrl: 'http://localhost:8080'), // Create an instance of ApiService with a base URL.
      child: MaterialApp( // A widget that configures the MaterialApp.
        home: BmiCalculatorApp(), // Set BmiCalculatorApp as the home screen of the app.
      ),
    ),
  );
}

class BmiCalculatorApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold( // Scaffold widget provides a standard app layout structure.
      appBar: AppBar( // AppBar is the top bar of the app that typically contains the title and actions.
        title: Text('BMI Calculator'), // Title of the AppBar.
      ),
      body: BmiCalculator(), // The main content of the app is BmiCalculator widget.
    );
  }
}

class BmiCalculator extends StatefulWidget {
  @override
  _BmiCalculatorState createState() => _BmiCalculatorState();
}

class _BmiCalculatorState extends State<BmiCalculator> {
  final TextEditingController _weightController = TextEditingController(); // Controller to manage weight input.
  final TextEditingController _heightController = TextEditingController(); // Controller to manage height input.
  String _message = "Please enter your height and weight"; // Initial message displayed to the user.
  Color _messageColor = Colors.black; // Color of the message text.

  @override
  void initState() {
    super.initState(); // super.initState() This method call ensures that the initialization process of the parent class (State) is executed. The parent class State may have some essential setup that needs to be done for your widget to function correctly.
    // No need to initialize ApiService here anymore
  }

  void _calculateBMI() async {
    double weight = double.parse(_weightController.text); // Get weight value from TextField.
    double height = double.parse(_heightController.text); // Get height value from TextField.

    try {
      // Use Provider.of to get ApiService instance.
      final ApiService apiService = Provider.of<ApiService>(context, listen: false); // Get ApiService instance.
      String result = await apiService.calculateBMI(weight, height); // Call calculateBMI method from ApiService.

      // Extract the BMI value from the result string.

      double bmi = double.parse(result.split(' ').last);

      // Determine the color based on the BMI value.
      if (bmi < 18.5) {
        _messageColor = Colors.orange; // Underweight
      } else if (bmi < 25) {
        _messageColor = Colors.green; // Healthy Weight
      } else if (bmi < 30) {
        _messageColor = Colors.red; // Overweight
      } else {
        _messageColor = Colors.grey; // Obese
      }

      setState(() {
        _message = result; // Update _message with the result.
      });
    } catch (e) {
      setState(() {
        _message = 'Error: $e'; // If an error occurs, update _message with error message.
        _messageColor = Colors.black; // Set color to black for error message.
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(16.0), // Padding around the entire Column widget.
      child: Column( // Column widget to arrange children widgets vertically.
        crossAxisAlignment: CrossAxisAlignment.stretch, // Stretch widgets horizontally.
        children: <Widget>[
          TextField( // TextField widget for weight input.
            controller: _weightController, // Controller to manage text input.
            decoration: InputDecoration( // Decoration for TextField.
              labelText: 'Weight (kg)', // Label text for TextField.
            ),
            keyboardType: TextInputType.number, // Allow numeric input.
          ),
          TextField( // TextField widget for height input.
            controller: _heightController, // Controller to manage text input.
            decoration: InputDecoration( // Decoration for TextField.
              labelText: 'Height (cm)', // Label text for TextField.
            ),
            keyboardType: TextInputType.number, // Allow numeric input.
          ),
          SizedBox(height: 20), // Empty space with a height of 20 pixels.
          ElevatedButton( // ElevatedButton widget for BMI calculation.
            onPressed: _calculateBMI, // Function to execute when button is pressed.
            child: Text('Calculate BMI'), // Text displayed on the button.
          ),
          SizedBox(height: 20), // Empty space with a height of 20 pixels.
          Text( // Text widget to display BMI calculation result or error message.
            _message, // Text to display.
            textAlign: TextAlign.center, // Center-align the text.
            style: TextStyle(fontSize: 24, color: _messageColor), // Font size and color of the text.
          ),
        ],
      ),
    );
  }
}

pubspec.yaml

remove all comments: 
name: bmi_calculator
description: "A new Flutter project."


version: 1.0.0+1

environment:
  sdk: '>=3.4.3 <4.0.0'

dependencies:
  flutter:
    sdk: flutter

  http: ^0.13.3
  provider: ^5.0.0

  cupertino_icons: ^1.0.6

dev_dependencies:
  flutter_test:
    sdk: flutter

  mockito: ^5.0.7
  
  flutter_lints: ^3.0.0

  uses-material-design: true

What I Have Tried:

  1. Using thenAnswer:

    when(mockApiService.calculateBMI(88, 186)).thenAnswer((_) async => '22.2');
    
  2. Using thenReturn with Generic:

    when(mockApiService.calculateBMI(any, any)).thenReturn(Future<String>.value('22.2'));
    
  3. Verifying Arguments:

    when(mockApiService.calculateBMI(88, 186)).thenAnswer((invocation) async {
      final int weight = invocation.positionalArguments[0];
      final int height = invocation.positionalArguments[1];
      return '22.2';
    });
    

Issue:

Despite these attempts, I am still encountering the type 'Null' is not a subtype of type 'Future<String>' error.My main.dart 

Question:

How can I resolve this TypeError and correctly mock the calculateBMI method to return the expected future value?

My Flutter program runs perfectly, but I can’t seem to pass the test in widget_test.dart

New contributor

pekran123 pekranian is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.

This looks like a problem with Mockito and Dart sound null safety.

As the NULL_SAFETY_README says, the following code is illegal under sound null safety for reasons of argument matching and return types. For details see the link.

class HttpServer {
  Uri start(int port)  {
    // ...
  }
}

class MockHttpServer extends Mock implements HttpServer {}

var server = MockHttpServer();
var uri = Uri.parse('http://localhost:8080');
when(server.start(any)).thenReturn(uri);

The same applies to you ApiServer mocking.

The readme offers two solutions: Code generation or manual mock implementation using reflection. In the latter case, the mock would look like

class MockHttpServer extends Mock implements HttpServer {
  @override
  void start(int? port) =>
      super.noSuchMethod(Invocation.method(#start, [port]));
}

From there, you can use when() or capture() as before.

For more complex mocks the code generation approach is preferable.

1

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa
Thiết kế website Thiết kế website Thiết kế website Cách kháng tài khoản quảng cáo Mua bán Fanpage Facebook Dịch vụ SEO Tổ chức sinh nhật