Making a Best Practice App #5 — Clean Architecture & Testing

Patryk Poborca
11 min readAug 27, 2015

--

See Dagger 2 here — If you are familiar with design patterns you may wish to skip down in the article to the testing segment.

Even though it’s possible to make tests for even the most poorly architected application, it’s important to understand that having business logic embedded in your Activity/Fragment/Custom View you will have to take additional steps to make sure you can even test the app. Case in point:

Say we want to test the functionality of fetching a tweet, in this scenario we would have to use Espresso to mock the user action of clicking the button, if the response occurs (is displayed in the view), etc. Not only is this dependent on Android, it’s also depends on Espresso, means convoluted tests, and longer load/deployment. Meaning that your build server running your tests each time your developers push will take longer and be more likely to fail tests in bizzarre ways.

This is where application architecture comes in. The purpose of application architectures is to modularize your business logic, data layer, and view layer so that you can test your application and not worry about each module’s implementation. In some ways it’s separate black boxes interacting with one another and reacting/responding. The base architectures that mesh well with Android are:

MVP — Model View Presenter

Simple explanation, the Model are classes which represent the data of your application, such as User Profiles, Messages, Reservations, trips, and any other objects useful in your application. In essence anything that can be represented in JSON, they may also contain helper methods. View is the Fragment/Activity/Custom view layer of Android, meaning it represent what gets rendered to the screen. Presenter is the object which dictates how the View is rendered and how it behaves. It also reacts to changes to the Data layer (Network/Db’s, Models) and pushes changes to the View.

The View has a direct reference to the Presenter and the Presenter has a reference of an interface which the View implements. The presenter may also implement an interface if you want total abstraction. The View should never have business logic inside of it, it should push input to the Presenter and wait to be told what to do by the presenter.

There are two variations of this, Passive view and Supervising Controller-Presenter. The passive view requires for the Presenter to be larger than some are comfortable, it’s a bit of a god object telling the view precisely what to render while still containing business logic.

Meanwhile the supervising controller will allow the View to react to Models being changed, and relying on direct instructions from the Presenter when it comes to complex logic. Thus allowing the View to be slightly more aware and the Presenter to be less bloated with code.

MVVM — Model View View Model.

Model is the same. However the View is in some ways even smarter than the Supervising variant of MVP, it has a reference to the View Model class. View Model is like the Presenter however it has no reference to the View. We NEED some sort of data-binding (See RxJava) in order to have the View react to changes to the View model/ Models since the View model has no reference to the view. This is a rapid development approach, however it is prone to mistakes where a user might embed business logic into the View making unit testing not as complete as it otherwise might be.

Interactors (MVP-I, MVVM-I)

This is not a stand alone architecture such as MVVM and MVP as much as best practice. It is important to understand that the View Model and Presenter can still become tightly coupled with 3rd party libraries such as networking/data caching/disk writing libraries and service APIs. This means that you can write wrappers around them so that if you swap the library you will not have to refactor the whole app, just the implementation of your wrapper. So what are Interactors? Interactors are bundles of several 3rd party libraries or wrappers around those 3rd party libraries. Each Interactor represents any DB/Network/etc calls the View Model/Presenter will need to make.

This is convenient for when you begin testing because even though you can mock your Networking library, or any other library for your whole test suite, sometimes you want your View Model/Presenter to have unique mocks of some call, you can simply create a MockInteractor which inherits from your VM/Presenter’s Interactor.

I think it’s helpful to look at the code of my Dagger 2/Clean architecture repository as there you can easily begin to tell the key differences between each implementation.

Repository

The above repo demonstrates no-architecture, MVVM and two variations of MVP (Passive view MVP, and Supervising Controller with an Interactor — MVPCI, stands for Model, View, Presenter Controller, Interactor)

Testing

This is what everything has been leading up to. We saw how Dagger 2 helped us decouple our dependencies from our app, but the advantages offered are not so amazing that you’re excited to jump on board the hype train. Assuming we architected our application to have our business logic separated away from our Activity/Fragment/Custom Views we run into the issue where during our tests we might rely on an Android library (meaning no POJO tests for us) or simply the network calls take long to come back and may be difficult to encapsulate their logic in a simple Assert.

This is where mock classes come into play, and it’s simple to do. Back in the previous article we had OKHttp, Retrofit, LocalDataCache, and TweeterAPI classes. These all had delays on their trxjava observables of 2 seconds. Meaning that their “lag” was simulated, but as in the real world when testing you would be hit by a 2*N (where N is number of API calls) lag on your tests. Since most test suites number in the dozens if not hundreds of tests this is simply unnacceptable. Every single second matters, additionally the LocalDataCache has a dependency on the Android object Context. This was done on purpose to show how mocking can allow us to decouple ourselves.

One last thing, before we can go about mocking the classes I listed earlier, in the previous article I had all of my RxJava observables observe on the

AndroidSchedulers.mainThread()

Because I wanted to demonstrate POJO JUnit tests I needed to remove the Android dependency. Thus I created the mainScheduler variable in my classes:

This required for me to change my NetworkModule since it was the provider of this class…

I utilize the @Named annotation, which we last spoke about in tail end of the previous article

Now if you remember in the Dagger 2 article, parameters must be provided by dependencies. As such I created a ThreadingModule.

Alright, with that background we can move on to the mocking of the classes I created to drive the application.

MockOkHTTP — Switched the raw response from some some generated UID to a static text “Mocked raw response:”

MockRetrofit — Removed the delay on both the completeRequest and fetchSomePage methods

MockLocalDataCache — Removed delay on fetchRecentTweets

MockTweeterApi — Removed delay on login/logout calls

Because the delay in our application was artificial it was simple to remove it, however in some cases you might just was want to replace the returned values with static ones such as I did with my mock OKHTTP. Alright, so we have removed delays and dependencies on Android from our “apis”. We will begin by setting up a folder structure as such:

/app/src/test/java

This is where our POJO tests will go. Next in order to have the proper IDE support and to run our tests correctly, on the bottom side of the left pane of Android studio you should see “Build Variants” click on this, and switch from AndroidInstrumentationTests to UnitTests. This allows us to compile and run the tests in the newly created directory.

I personally create a folder structure as such

As you can see we will be working with Dagger again, we put our mocked classes (which I listed above) inside of mockimpl, tests go in their own folder. And finally we have this helper folder. In order to make my test classes less bloated I created a quick little helper. This class does all of the Dagger building allowing us to keep lines of code required for Dagger to a minimum. Additionally it has a little helper method called “waitFor” which allows us to wait for work that may be done Async due to RxJava. This is important for my androidTests implementations.

To run these tests, simply right click the “java/tests” folder and click run.

In the above code you can see though that I’m providing mock modules to my builders. This is because this allows me to inject my mock classes into my Presenters/ViewModels. Please go to my repository if you want to take a look at their implementation. I do not want to clutter the article with code more than I have to. Alright onto the tests. First I’m going to look at the no architecture implementation to show you why it’s bad [source code here]. For one, you cannot add any tests for it into the src/test directory because you cannot decouple its Android dependencies. This means we need to run it on an Android device and put it into the src/androidTest directory. Next, we will have to utilize Espresso to mock user interaction with our Activity and check the View to see if the API’s did thier job and the View was changed accordingly. You can see the test down below.

So a little bit about this and all other tests. Each method labeled with a @Test annotation is considered to be a “test” I could clump them all together into one large method. However it’s best to split the tests into logical groupings. Such as a test for testing Tweets, Login, etc.

AndroidJunit runner will call each test method found, and see if all the Asserts it contains are passed. Additionally it will inform you of any exceptions as well. In cases such as ours, we want to set up the class to be ready to be tested, that is where the @Before annotation comes in. This method will be called BEFORE any test methods. There’s a matching @After annotation which you can use to tear down any objects that you don’t want persisting between your Tests.

Finally, there are “Rules” in tests, these are written in order to have reusable logic between tests. So instead of having to utilize the Before/After annotation each time for a rule, these @Rule annotated Rules will hook into the Junit’s Before/After calls. This is simply a reusable code for tests.

So back to the test, as you can see in my @Before method I use my TestHelper to inject my Activity object, then I check to see if the View behaves as expected, checking to see if Views get populated, and with what, if my LoginContainer gets shown/revealed upon login and logout.

My MockRetrofit/MockTweeterApi get directly injected into the Activity and that’s that. So what are the problems with this? I have to mock user interaction, inspect views to see if they changed, and worst of all I’m working with Espresso to test business logic. It’s ugly and prone to errors. Average test runtime= ~3 seconds on Genymotion on my computer for JUST this the listed tests. Accounting for my other AndroidTests for MVVM, MVPCI, and MVP my test take between ~20 seconds to compile, deploy, and run.

Let’s take a look at the MVVM test to see if there’s a light at the end of the tunnel of testing. For this we can actually utilize the src/test directory. This is using POJO, meaning no Android dependencies.

In this case, we have our MVVMTest class plays the role of the TweeterActivity and binds to the ViewModel’s observables. We simply check what the ViewModel sends our way and use Assert to check to see if it’s what we expect. We check to see if fetching a tweet works, then see if it comes up in a recent tweet list, see if logging in / out works. It’s all very straight forward and not convoluted. In my mind MVVM tests are the easiest to work with, and which is why I think of MVVM as the rapid development architecture. That’s it, it’s quick and concise.

As you can see, the decoupling of View and Business logic makes testing more direct and easy to read. It is important to see how testing differs between MVVM and MVP. Since in MVVM we simply subscribe to the observables provided to us by the view model, we don’t have to worry about the View logic too much. However in MVP we don’t utilize data binding between the view nad presenter we must worry about the callbacks featured in the View’s interface.

In MVP, we must create a MockView which the view of MVP implements. This is important because in MVP variants the view is told what to do via method calls which it inherits from the interface. So first we create a mock view (Alternatively we could’ve had our Test class implement the view’s interface)

As you can see, the mock is mostly just a wrapper filled with methods which simply have a flag (Boolean or Object) which you can utilize to to check if the method was called or determine if a certain result occurred besides the method being called. In the test below that I check what I expect, against what the mock view receives.

One more thing, the purpose of the TestHelper.waitFor method is to allow the async calls from RxJava to occur. Since we removed the Delay it should be near instant, the waitFor checks every 5 ms, it doesn’t even make it make it to 10 ms (Waits for up to 50 ms by default). It seems to happen only in the emulated android phone because it’s slower then its POJO Junit test counter parts. I kept the waitFor in the POJO tests though because if it appears to be a race-condition and I want my tests to be consistent.

So what’s the purpose of these ‘Tests’?

Well it’s pretty simple if you think about it for a moment. Tests are useful because you want to:

  1. Think through your application and make sure the business logic is indeed testable and readable. Meaning that you didn’t write obscure code and you also put the code inside the proper module (The presenter, View Model as opposed to in the View)
  2. You want to make sure the application behave how you, THE PROGRAMMER expects it to. Case in point, while I was working on this demo application it slipped my attention that the fetch-recent tweets operation was incorrect and was simply returning the oldest tweets. Not the newest.
  3. For later down the road if there’s a application update you push, or a change to the underlying libraries you use (say you switch from Retrofit to Volley or something of that sort) you want a test which tells you if the app is still working like it was before the updates. Simply put the tests will start breaking telling you that thing aren’t working how you expect them to.

Alright, thank you for reading through my series, I hope it’s informative and helpful. If you have any questions or comments (ahem.. even hate mail) feel free to tweet me also I’d appreciate if you follow me too. Or simply make a comment here!

Looking for an Android Contractor? Look no further, contact me at: info@koziodigital.com

--

--