square / sqlbrite

A lightweight wrapper around SQLiteOpenHelper which introduces reactive stream semantics to SQL operations.

Home Page:https://square.github.io/sqlbrite/3.x/sqlbrite/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Make particular classes non-final

fat-fellow opened this issue · comments

Would you be so kind to make classes like BriteDatabase, QueryObservable non-final for testing purposes?
PowerMock doesn't work with Mockito2.
Mockito2 final classes mocking feature is still in development and doesn't work good enough.
It is a bit tricky in this case to mock queried data.
Actually i needed to have db object wrapped and I then i had to use filled cursor to mock query map to list. It is not that convenient.

You don't need to mock QueryObservable since it has a public constructor and you can create instances with whatever behavior you want like this.

As to BriteDatabase, I'm not convinced that we should allow mocking. It's a complex API that if mocked would almost certainly lead to you writing a test that verified the interactions and arguments used. This would be a bad test because any change to those interactions or arguments would require the test to change–even if the resulting behavior didn't change. If you pass null as the DB path to your open helper an in-memory database will be used which lets you run your tests against the actual SQLite implementation without worrying about files on disk. This ensures you're testing against real behavior and not mocked behavior and also that you're not scripting database interactions. Conversely, if you do want to mock the database, I would recommend mocking at a higher level abstraction which is much more declarative to your domain instead of your database. That is, there should be a layer above BriteDatabase with methods like addUser(User), for example, which don't leak implementation details about the database itself into the API and allow your tests to mock out the entire storage layer instead of trying to just mock a database layer.

Thank you for your attention!
I can agree with you about BriteDatabase, although i am separating testing for working with data models and interaction with db.
I already have tests for a mapper (save / restore)
And for example I have an interactor with BriteDatabase inside.
I am testing interactor, and like with testing of interaction with retrofit api, sometimes you just need to test reaction to the "external" services. So we are mocking the retrofit interface, and testing reaction of our interactor to whatever mocked interface "sends" to us.
So when i need the same behaviour from db, i loose it.
I am testing not the db, just the reaction to the data it sends to me.
For example I can't just mock Db.createQuery and use QueryObservable, that "sends" us a data via the (mocked) method mapToList. 'Cause these classes and methods are final.
All the flow goes through rx and I believe I don't really need to have any real db in my interactor.
Code example (I need to emulate in the test a reaction to a restored list of SerpElement:

override fun getSerpElements(start: Int, limit: Int): Observable<LoadingState<List<SerpElement>>> {
       return db
               .createQuery(CacheTable.TABLE, PAGE_QUERY, key, limit.toString(), start.toString())
               .mapToList(SerpElementMapper())
               .map { LoadingState.Loaded(it) as LoadingState<List<SerpElement>> }
               .subscribeOn(schedulers.io())
               .startWith(LoadingState.Loading())
               .onErrorReturn { LoadingState.Error(throwableConverter.convert(it)) }
   }

I believe it is not necessity or convenience of any kind to create one another layer when using rxjava, but seems the possibility of mocking can be a very nice feature in this case
I hope you understand my point.

Sorry for the long delay on this, but we're not going to be making this change.

The sample code you outlined conflates two things which is why it's challenging to test. You're interacting with the database via SQL and your presenting that interaction to the UI. These are two separate concerns and those concerns should be reflected in the code structure.

If these were separate, you could test the SQL with an in-memory DB integration test to ensure it produces the right outputs after being mapped to a list. The UI presentation could then be separately tested using an dummy observable that returned a fake list and ensured the correct interactions were performed.