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

3,215

Solution 1

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

Solution 2

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.

Share:
3,215
Ovidius Mazuru
Author by

Ovidius Mazuru

Updated on December 21, 2022

Comments

  • Ovidius Mazuru
    Ovidius Mazuru over 1 year

    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