Flutter test find by sub text - unit-testing

Is it possible to assert a test if the certain text is found either as full string match or as a sub string of other text?
I want the following test to pass as long as the string Foo is found anywhere, even as sub string of other strings. What should I change?
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
testWidgets('Find sub text', (WidgetTester tester) async {
await tester.pumpWidget(Text('Error code is Foo', textDirection: TextDirection.ltr));
expect(find.text('Error code is Foo'), findsOneWidget);
expect(find.text('Foo'), findsOneWidget);
});
}
So far:
The following TestFailure object was thrown running a test:
Expected: exactly one matching node in the widget tree
Actual: _TextFinder:<zero widgets with text "Foo" (ignoring offstage widgets)>
Which: means none were found but one was expected

You can use find.textContaining:
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
testWidgets('Find sub text', (WidgetTester tester) async {
await tester.pumpWidget(Text('Error code is Foo', textDirection: TextDirection.ltr));
expect(find.text('Error code is Foo'), findsOneWidget);
expect(find.textContaining('Foo'), findsOneWidget);
});
}

Replace:
expect(find.text('Foo'), findsOneWidget);
with:
expect(find.byWidgetPredicate((widget) {
if (widget is Text) {
final Text textWidget = widget;
if (textWidget.data != null) return textWidget.data.contains('Foo');
return textWidget.textSpan.toPlainText().contains('Foo');
}
return false;
}), findsOneWidget);
You will first find Text widget, then check for substring in the Text string

Related

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
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.
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
You should mock the hive interface and box;

Chain multiple calls with same arguments to return different results

I'm in the process of writing a Flutter app with some extensive unit test coverage.
I'm using Mockito to mock my classes.
Coming from a Java (Android) world where I can use Mockito to chain calls to return different values on subsequent calls.
I would expect this to work.
import 'package:test/test.dart';
import 'package:mockito/mockito.dart';
void main() {
test("some string test", () {
StringProvider strProvider = MockStringProvider();
when(strProvider.randomStr()).thenReturn("hello");
when(strProvider.randomStr()).thenReturn("world");
expect(strProvider.randomStr(), "hello");
expect(strProvider.randomStr(), "world");
});
}
class StringProvider {
String randomStr() => "real implementation";
}
class MockStringProvider extends Mock implements StringProvider {}
However it throws:
Expected: 'hello'
Actual: 'world'
Which: is different.
The only working way I found that works is by keeping track myself.
void main() {
test("some string test", () {
StringProvider strProvider = MockStringProvider();
var invocations = 0;
when(strProvider.randomStr()).thenAnswer((_) {
var a = '';
if (invocations == 0) {
a = 'hello';
} else {
a = 'world';
}
invocations++;
return a;
});
expect(strProvider.randomStr(), "hello");
expect(strProvider.randomStr(), "world");
});
}
00:01 +1: All tests passed!
Is there a better way?
Use a list and return the answers with removeAt:
import 'package:test/test.dart';
import 'package:mockito/mockito.dart';
void main() {
test("some string test", () {
StringProvider strProvider = MockStringProvider();
var answers = ["hello", "world"];
when(strProvider.randomStr()).thenAnswer((_) => answers.removeAt(0));
expect(strProvider.randomStr(), "hello");
expect(strProvider.randomStr(), "world");
});
}
class StringProvider {
String randomStr() => "real implementation";
}
class MockStringProvider extends Mock implements StringProvider {}
You're not forced to call when in the start of the test:
StringProvider strProvider = MockStringProvider();
when(strProvider.randomStr()).thenReturn("hello");
expect(strProvider.randomStr(), "hello");
when(strProvider.randomStr()).thenReturn("world");
expect(strProvider.randomStr(), "world");
Mockito is dart has a different behavior. Subsequent calls override the value.
Option 1 - With a Stub
I'm not sure about mockito, but you could do it using argument matchers, and creating a stub class that overrides that method instead of using a mock, like:
class StrProviderStub implements StrProvider {
StrProviderStub(this.values);
List<String> values;
int _currentCall = 0;
#override
String randomStr() {
final value = values[_currentCall];
_currentCall++;
return value;
}
}
And then use that Stub instead of the mock on the test
Option 2 - Use an Extension with a closure (Preferred)
extension MultipleExpectations<T> on PostExpectation<T> {
void thenAnwserInOrder(List<T> bodies) {
final safeBody = Queue.of(bodies);
thenAnswer((realInvocation) => safeBody.removeFirst());
}
}
# Then... use it like:
when(mock).thenAnwserInOrder(['First call', 'second call']);
I opened this issue to see if this could be added to the framework itself https://github.com/dart-lang/mockito/issues/568

Turkish uppercase dotted i issue in Angular 2 capitalization pipe

I have a capitalization pipe. Almost all characters are capitalized. Turkish 'ı' character is converted correctly into 'I'. However, the 'i' character is converted to 'I' when it should be converted into the 'İ' character.
Example 1: ırmak => Irmak (Correct).
Example 2: ismail => Ismail (Incorrect, should be İsmail).
My code is below:
import { Pipe, PipeTransform } from '#angular/core';
#Pipe({name: 'capitalize'})
export class CapitalizePipe implements PipeTransform {
transform(value: string, args: string[]): any {
if (!value) return value;
return value.replace(/[çğıöşüa-zA-z]\S*/g, function(txt) {
return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
});
}
}
First, create a new file and name it "titlecase-turkish.pipe.ts".
Open the file and add the following code:
import { Pipe, PipeTransform } from '#angular/core';
#Pipe({
name: 'titlecaseTurkish'
})
export class TitleCaseTurkishPipe implements PipeTransform {
transform(value: string): string {
if (!value) return null;
let words = value.split(' ');
for (var i = 0; i < words.length; i++) {
words[i] = words[i].toLocaleLowerCase('tr-TR');
words[i] = words[i].charAt(0).toLocaleUpperCase('tr-TR') + words[i].slice(1);
}
return words.join(' ');
}
}
The above code implements the basic functionality of the TitleCaseTurkishPipe. It splits the incoming string value by spaces and converts the first letter of each word to uppercase while converting all other letters to lowercase. To correctly capitalize Turkish characters, the toLocaleLowerCase() and toLocaleUpperCase() methods have been used.
To use the TitleCaseTurkishPipe, you need to first declare it in the AppModule. Add the following code to do so:
import { NgModule } from '#angular/core';
import { BrowserModule } from '#angular/platform-browser';
import { FormsModule } from '#angular/forms';
import { AppComponent } from './app.component';
import { TitleCaseTurkishPipe } from './titlecase-turkish.pipe';
#NgModule({
imports: [BrowserModule, FormsModule],
declarations: [AppComponent, TitleCaseTurkishPipe],
bootstrap: [AppComponent]
})
export class AppModule { }
You can now use the TitleCaseTurkishPipe in your HTML file. For example, the following code formats the value of the "name" variable using the TitleCaseTurkishPipe:
<h1>{{ name | titlecaseTurkish }}</h1>

How to add a mask 'yyyy/yyyy' in angular2, using pipe and formatter to input field

I'd like to know how can I create a mask with regex expression for input field.
I need to add some masks to a field, such as: yyyy/yyyy. This sounds like a period.
I saw a link that creates a mask using the pipe from Angular2. Here is the link.
So, I'd like to create a pipe with a different regex expression to allow the user writes only this: yyyy/yyyy ; and using transform method from pipe.
Is this possible?
Here is my pipe and formatter:
import { Pipe, PipeTransform } from "#angular/core";
#Pipe({ name: "mypipe" })
export class MyPipe implements PipeTransform {
private SEPARATOR: string;
constructor() {
this.SEPARATOR = "/";
}
transform(value): string {
let integer = (value || "").toString();
// Here is where the code should be, to transform the value
return integer;
}
transform(value): string {
// parse method
}
}
import { Directive, HostListener, ElementRef, OnInit } from "#angular/core";
// import { MyPipe } from ...
#Directive({ selector: "[myFormatter]" })
export class MyFormatterDirective implements OnInit {
private el: HTMLInputElement;
constructor(
private elementRef: ElementRef,
private mypipe: MyPipe
) {
this.el = this.elementRef.nativeElement;
}
ngOnInit() {
this.el.value = this.mypipe.transform(this.el.value);
}
#HostListener("keydown", ["$event.target.value"])
handleKeyboardEvent(value) {
this.el.value = this.mypipe.transform(value);
}
}

Angular2 unit testing a switchMap

Simple question:
I've followed the tutorial: here where I have the following code:
this.term.valueChanges
.debounceTime(400)
.distinctUntilChanged()
.switchMap(term => this.wikipediaService.search(term));
Where term is a Control inheriting from AbstractControl, how can I trigger the valueChanges observable property, so that I can perform unit tests?
I think I was experiencing the same issue.
I was trying to test that heroService.searchHeroes was called:
ngOnInit() {
this.heroes$ = this.searchTerms
.pipe(
debounceTime(300),
distinctUntilChanged(),
switchMap((term: string) => this.heroService.searchHeroes(term))
);
}
I was able to fix it by only calling tick(500) once
it('should call HeroService.searchHeroes', fakeAsync(() => {
spyOn(heroService, 'searchHeroes').and.returnValue(component.heroes$);
let searchField: DebugElement = fixture.debugElement.query(By.css('#search-box'));
searchField.nativeElement.value = 'i';
searchField.nativeElement.dispatchEvent(new Event('keyup'));
tick(500);
expect(heroService.searchHeroes).toHaveBeenCalled();
}));
I found a work around in the following issue:
https://github.com/angular/angular/issues/8251
Essentially there's buggy behavior, but you can get a basic test working with code like this:
it("filter input changed should update filter string",
fakeAsync(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => {
let fixture = tcb.createFakeAsync(MyComponent);
let component = fixture.componentInstance as MyComponent;
fixture.detectChanges();
// Trigger a new value to emit on the valueChanges observable
//
component.filter.updateValue("testing");
// There's buggy behavior with multiple async events so we
// need to run tick() twice to get our desired behavior
// https://github.com/angular/angular/issues/8251
//
tick();
tick(500);
// Now we can inspect the results of the subscription in the
// original component
//
expect(component.filterString).toBe("testing");
})));
...and this is with a component that looks like this:
export class MyComponent {
filter = new Control();
/** The current filter string */
filterString: string;
constructor() {
// Subscribe to changes in the filter string
//
this.filter.valueChanges
.debounceTime(200)
.distinctUntilChanged()
.subscribe((filterString: string) => {
this.filterString = filterString;
});
}
}