Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I test / mock Hive (Flutter) open box logic in repo?

Sorry if this seems a dumb question. I'm learning clean architecture as dictated by Rob Martin, and I've having a tiny bit of trouble writing one of my tests.

I wrote a couple functions in a Hive repo. Here's the code

import 'package:hive/hive.dart';
import 'package:movie_browser/features/SearchMovie/domain/entities/movie_detailed_entity.dart';

abstract class HiveMovieSearchRepoAbstract {
  Future<void> cacheMovieDetails(MovieDetailed movie);
  Future<MovieDetailed> getCachedMovieDetails(String id);
}

// const vars to prevent misspellings
const String MOVIEDETAILSBOX = "MovieDetailedBox";
const String SEARCHBOX = "SearchBox";

class HiveMovieSearchRepo implements HiveMovieSearchRepoAbstract {
  Box movieDetailsBox = Hive.box(MOVIEDETAILSBOX) ?? null;
  // TODO implement searchbox
  // final searchBox = Hive.box(SEARCHBOX);

  Future<void> cacheMovieDetails(MovieDetailed movie) async {
    /// expects a MovieDetailed to cache.  Will cache that movie
    movieDetailsBox ?? await _openBox(movieDetailsBox, MOVIEDETAILSBOX);

    movieDetailsBox.put('${movie.id}', movie);
  }

  Future<MovieDetailed> getCachedMovieDetails(String id) async {
    /// expects a string id as input
    /// returns the MovieDetailed if cached previously
    /// returns null otherwise
    movieDetailsBox ?? await _openBox(movieDetailsBox, MOVIEDETAILSBOX);

    return await movieDetailsBox.get('$id');
  }

  _openBox(Box box, String type) async {
    await Hive.openBox(type);
    return Hive.box(type);
  }
}

I can't think of how to test this? I want two cases, one where the box is already opened, and one case where it isn't.

Specifically, it's these lines I want to test

movieDetailsBox ?? await _openBox(movieDetailsBox, MOVIEDETAILSBOX);

_openBox(Box box, String type) async {
    await Hive.openBox(type);
    return Hive.box(type);
  }

I thought about mocking the Box object then doing something like....

when(mockHiveMovieSearchRepo.getCachedMovieDetails(some_id)).thenAnswer((_) async => object)

but wouldn't that bypass the code I want tested and always show as positive?

Thanks so much for the help

like image 996
Ovidius Mazuru Avatar asked Jun 11 '20 19:06

Ovidius Mazuru


People also ask

How do you use the hive box in Flutter?

With hive, before you can read/compose data, a box should be opened. Boxes can be opened with await Hive. Openbox('name') can get an instance of an opened box with Hive. Box ('name'), where 'name' is the name of the case (saying the DB name). You can call Hive.

What types of tests can you perform in Flutter?

Once the app is complete, you will write the following tests: Unit tests to validate the add and remove operations. Widgets tests for the home and favorites pages. UI and performance tests for the entire app using integration tests.


Video Answer


2 Answers

i don't know if i fully understand your question but you can try something like this

abstract class HiveMovieSearchRepoAbstract {
  Future<void> cacheMovieDetails(MovieDetailed movie);
  Future<MovieDetailed> getCachedMovieDetails(String id);
}

// const vars to prevent misspellings
const String MOVIEDETAILSBOX = "MovieDetailedBox";
const String SEARCHBOX = "SearchBox";

class HiveMovieSearchRepo implements HiveMovieSearchRepoAbstract {
  final HiveInterface hive;

  HiveMovieSearchRepo({@required this.hive});

  @override
  Future<void> cacheMovieDetails(MovieDetailed cacheMovieDetails) async {
    /// expects a MovieDetailed to cache.  Will cache that movie
    try {
      final moviedetailbox = await _openBox(MOVIEDETAILSBOX);
      moviedetailbox.put('${movie.id}', movie);
    } catch (e) {
      throw CacheException();
    }
  }

  Future<MovieDetailed> getCachedMovieDetails(String id) async {
    /// expects a string id as input
    /// returns the MovieDetailed if cached previously
    /// returns null otherwise
    try {
      final moviedetailbox = await _openBox(MOVIEDETAILSBOX);
      if (moviedetailbox.containsKey(boxkeyname)) {
        return await movieDetailsBox.get('$id');
      }
      return null;
    } catch (e) {
      return CacheException();
    }
  }

  Future<Box> _openBox(String type) async {
    try {
      final box = await hive.openBox(type);
      return box;
    } catch (e) {
      throw CacheException();
    }
  }
}

And to test it you can do something like this

class MockHiveInterface extends Mock implements HiveInterface {}

class MockHiveBox extends Mock implements Box {}

void main() {
  MockHiveInterface mockHiveInterface;
  MockHiveBox mockHiveBox;
  HiveMovieSearchRepo hiveMovieSearchRepo;
  setUp(() {
    mockHiveInterface = MockHiveInterface();
    mockHiveBox = MockHiveBox();
    hiveMovieSearchRepo = HiveMovieSearchRepo(hive: mockHiveInterface);
  });

  group('cacheMoviedetails', () {

    
    test(
    'should cache the movie details',
     () async{
        //arrange
         when(mockHiveInterface.openBox(any)).thenAnswer((_) async => mockHiveBox);
        //act
      await hiveMovieSearchRepo.cacheMovieDetails(tcacheMovieDetails);
        //assert
      verify(mockHiveBox.put('${movie.id}', tmovie));
      verify(mockHiveInterface.openBox("MovieDetailedBox"));
      });
    
  });

  group('getLocalCitiesAndCountriesAtPage', () {
    test('should when', () async {
      //arrange
      when(mockHiveInterface.openBox(any))
          .thenAnswer((realInvocation) async => mockHiveBox);
      when(mockHiveBox.get('$id'))
          .thenAnswer((realInvocation) async => tmoviedetails);
      //act
      final result =
          await hiveMovieSearchRepo.getCachedMovieDetails(tId);
      //assert
      verify(mockHiveInterface.openBox(any));
      verify(mockHiveBox.get('page${tpage.toString()}'));
      expect(result, tmoviedetails);
    });
  });

}

You should add some tests also for the CacheExeption(). Hope this help you.

like image 52
Francisco Cordeiro Avatar answered Oct 16 '22 08:10

Francisco Cordeiro


So, I wrote this post 9 months. Stackoverflow just sent me a notification saying it's a popular question, so I'll answer it for anyone else wondering the same thing

Easy way to make this testable is change Box to an arg passed into the class, like so

abstract class ClassName {
  final Box movieDetailsBox;
  final Box searchBox;

  ClassName({
    this.moveDetailsBox,
    this.searchBox,
  });
}

this makes the boxes mockable and testable

like image 1
Ovidius Mazuru Avatar answered Oct 16 '22 07:10

Ovidius Mazuru