I've started unit testing the first time. I'm following tutorials of resoCoder
here is my test code where I'm mocking my dbManager class but I could not mock DAO's as they are auto-generated in moor and there is no setter method for them.
class MockDbManager extends Mock implements DbManager{}
void main() {
RecipeLocalDataSource dataSource;
MockDbManager _dbManager;
setUp(() {
_dbManager = MockDbManager();
dataSource = RecipeLocalDataSource(_dbManager);
});
group('Search Food Table', (){
List<FoodTableData> getFoodTable(){
var list = [];
for(var i =1; i <=5 ; i++){
list.add(FoodTableData(id: i, name: 'item $i'));
}
return list;
}
var searchQuery = 'query';
test('Should return foodTableData when query is successful', (){
//arrange
when(_dbManager.foodTableDao.searchFoods(searchQuery)).thenAnswer((realInvocation) async => getFoodTable());
//act
var result = dataSource.searchFoodTable('test');
//assert
verify(_dbManager.foodTableDao.searchFoods(searchQuery));
expect(getFoodTable(), result);
});
});
}
I'm getting the following error
NoSuchMethodError: The method 'searchFoods' was called on null.
Receiver: null
Tried calling: searchFoods("query")
I understood the error but don't know how to solve that.
Also, I'm getting a similar issue with preferenceManager class as well, where I'm having a getter for UserPrefs.
UserPrefs get user => UserPrefs(_pref);
when I'm accessing _prefManager.user.name
for the test, it throws the same error. How can I tackle that as well?
I believe you're missing a level of mocking, leading to the null exception. Keep in mind the mocked class returns null for everything. You have to provide values for everything.
You've mocked DbManager, but not the foodTableDao field inside DbManager.
// I don't have your classes, so these are just my guesses at matching their interface
abstract class TableDao {
String searchFoods();
}
abstract class DbManager {
TableDao foodTableDao;
}
class MockDbManager extends Mock implements DbManager {}
class MockTableDao extends Mock implements TableDao {}
// ↑ Define this mocked class as well
void main() {
test('Mockito example', () {
final dbManager = MockDbManager();
final mockTableDao = MockTableDao();
// ↑ instantiate this mocked class which will be a field value inside dbManager
// Mock the field access for dbManager.foodTableDao
when(dbManager.foodTableDao).thenReturn(mockTableDao);
// ↑ You're missing this ↑ level of stubbing, so add this
when(mockTableDao.searchFoods()).thenAnswer((realInvocation) => 'Cucumber');
// ↑ define the stubbing at the mockTableDao level, not dbManger.mockTableDao.searchFoods
expect(dbManager.foodTableDao.searchFoods(), 'Cucumber');
// You can use foodTableDao field here, getting the mocked value as expected
});
}
In your example above, you can't access dbManager.foodTableDao
to set up a mock for dbManager.foodTableDao.searchFoods()
, because dbManager.foodTableDao
is null until you mock it. So this line:
when(_dbManager.foodTableDao.searchFoods(searchQuery)).thenAnswer((realInvocation) async => getFoodTable());
was throwing the null exception, not the expect
test below. (The expect test was never reached.)
The issue with _prefManager.user.name
is the same I'm guessing. You need to mock User class and provide a when/return
of MockUser for _prefManager.user in order to provide another level of when/return
for _prefManager.user.name
.
It's like Mockception... you need to go another level deeper in your mocks. ;)