How to Get Your App Users to Stick Around After the Free Trial

By: Carol Vercellino, CEO & Co-Founder


Free trials are a great way to show users why they should choose your app or software product over anything else.

But how do you ensure your users will renew their subscriptions once the free trial is up?

Today, I’m going to share three ways you can get your users to commit after their free trial has ended. 


Nail your onboarding process

How do you nail the onboarding process? There are three ways you can focus on this.

First, you want to create a help center on your website or have a small support team. You could even have part-time folks who can answer technical questions. Make it easy for your users to find you; for example, your support email should be

Second, send users a welcome email with all the information they need about their free trial and how to download the product to get started. You could even turn that process into a welcome cadence where you send them an email once every two days or every day for the first seven days.

Third, you also want to provide in-app native FAQs so your users can open the app, click Help, and quickly get the answers they need.

Empower your users

To get your app or product’s full benefits, your users need to know all the ins and outs. Empowering your customers with educational materials about your product can boost customer loyalty and reduce complaints.

So, first off, you want to offer your users hands-on experience with live demos. If you’re a startup, you want to do this person-to-person, for example, over Zoom, and ask your users how things are doing and if you can answer any questions.

Second, you can create and share useful content on your website, email, and social media. It can be simple how-to’s or tech tips to support your user in achieving their goals through your product.

You can also organize a virtual workshop or webinar. You can do those live or have them pre-recorded so your users can access them anytime via your website.

Make it easy for users to buy your app

This step often gets overlooked, but it’s the most important. Make it easy for your users to buy your app or software product once the trial is up.

Remind users their free trial is about to expire. You can send your users an email or maybe even call them if you’re a startup and have the team to do it. 

You can also provide an early discount if they buy before the expiration of the trial. For example, you can send them an email and say, ‘Hey, if you renew now (7 days before your trial is up), we’ll take 20% of the subscription!’

Also, send a final email that includes step-by-step how users can buy and activate the full version. Make the purchasing process really easy. Find the simplest checkout form that you can have them put their credit card information in (if you can, use Apple Pay). If the product is available in the app store, that makes it pretty easy too.


Your free trial phase is a great way to retain customers, build loyalty, and grow your business.

Make it easy for your free trial users to learn how to use your app or software to its fullest potential, and they’ll move right into the paying phase.


Now that you’ve got your users hooked, learn how to streamline your onboarding process, so your app users never want to leave. Read it here.

By: Carol Vercellino, CEO & Co-Founder


Today my co-founder and business partner Jay Lyerly and I are going to talk about adjustments you may need to make to your app as the year winds down. So, specifically, Apple’s app store tends to shut down in December, so we’re going to discuss how you can adjust your workflow and processes to ensure that the end of the year can be seamless for you and your app users. 

Watch the video or read throughout transcription to learn what this closure means for developers.


Carol: So Jay, what’s the closure about? What does it really mean for developers?

Jay: What usually happens is Apple closes down its app store review around Christmas. I think last year it was from the 23rd to the 27th, so a couple of days before a couple of days after [Christmas]. That just means they aren’t accepting new apps. They aren’t doing the review process, so new apps can’t go into the store during that period. That means for a developer, if you want to get something out before the end of the year, you definitely have to get in before that window. There might be a bit of a rush right before then because a lot of developers are trying to get that last release out for the year.

The other thing to think about is if you’re going to do a big new release with lots of features, you might want to do that a little early in December. Then you have some cushion in case something goes horribly wrong and you need to get out a critical bug fix. Not that that ever happens, but just in case!


Carol: What might be a good amount of buffer time? Is it a couple of days? Two weeks?

Jay: For me I think December 15 might be a drop-dead date to get anything big out. That gives you a week or so for any critical bugs to pop up.


Carol: That makes sense. Is there anything people need to do now, or just know that the date is coming up?

Jay: Well it’s one of those things to know is coming up. Typically Christmas is one of the biggest days of the year for app installs. People get their new device, so they install a bunch of apps. They see family and say, “Oh, have you tried this new app?” So there are a lot of installs that happen that day, so a lot of people do try to get a new feature out and do a little PR around it before the holidays.


Carol: Okay, that makes sense. And I feel like last year and probably years before that I actually have seen Black Friday specials for app downloads. Is that something you’ve seen as well.

Jay: I don’t know off-hand, but that certainly seems like a thing that will happen!


Carol: Yeah, I want to say that I got the Things app on a Black Friday special last year. So it’s definitely something people can look out for and consider in their pricing model as well. Anything else people should know?

Jay: I think that mostly covers it. Just don’t get caught blindsighted – “Why can’t I submit my app? OH, they’re closed for a week”


If you need help updating or launching your app before the closer, just email us at 

by Jay Lyerly, CEO of Oak City Labs

According to new research, Americans check their phones every 12 minutes. And what are they doing on their phone? Well, 90% of their mobile time is spent on apps.

Pretty good news for someone like you wanting to build an app, right? But, if you want to make some headway in this multi-billion dollar industry, two things need to happen:

  1. Your app needs to work.
  2. People need to enjoy using your app. 

Sounds simple and obvious, but knowing is easier than executing. If you want to compete and succeed, you need to have a better understanding of how to build a functional app that can grow with you and will get you 5-star reviews all day long in the app store. 

And it all starts with unit testing. Keep reading to learn the reasons why it’s important to test your app early during development and what questions to ask a developer who’s building your application.

Why Mobile Application Testing is Important

Reason 1: It helps you manage risks

At Oak City Labs, we see applications come in all the time with absolutely no history of testing.

Most of the time, it’s due to working with an inexperienced developer, someone who’s early on in their career and doesn’t understand the value of testing. And sometimes, it’s the organization that doesn’t understand how necessary testing is, and slices it out of the scope to save some coin.

However, of all the steps you take during your app development process, and all the services you invest in to build your app, testing is one of the most important.

Testing helps you manage risks. That’s really what it’s all about.

As your product grows and develops new features, it becomes more complex. If you have solid unit testing, you can rest assured that as you add new features, you’re not going to break existing functionality.

Testing also lets you know that your app will continue to work as expected as your features and product evolve.

Reason 2: It fixes your bug problems

Much like glue traps and pest repellent spray, testing helps your app developer monitor your app for bugs and create tests to address them.

For example, with apps we’re building, if we encounter a bug, we write a test for it (the purpose of that test is to fail), and that lets us illustrate to ourselves and our clients what went wrong.

After we fix the bug, we conduct another test to demonstrate that the solution we chose was the correct one and will ensure the bug doesn’t reoccur in the future.

Reason 3: It helps you stay in compliance

With a health and wellness app, you’re likely in an environment where you need to stay in compliance with HIPAA and other regulations. And by codifying some of those compliance workflows and rules in your unit testing, you can make sure that as you grow, and your app becomes more complex, you stay in compliance. 

Questions to Ask Your Developer About Unit Testing

As you can see, it’s important to start testing early.

As soon as you start writing code that’s not tested, you begin to dig a hole of technical debt. The more code you write, the bigger that hole gets. Later on, you’re going to spend a lot of time (and money) filling that hole back in.

Many codebases that don’t have code fall into this trap. Their code has gotten too big, and the developer can’t fill it in. So, it’s important to stay ahead of the game with your testing.

When you’re talking to a developer who might work with you on building your application, here are some questions you can ask to learn more about how much value they place on unit testing and the process for doing so:
• Do you test?
• What kind of testing do you do?
• When do you test? 

Testing is an investment in the future of your health & wellness app.

Unit testing takes work, but it’s important to ensure you have a stable and reliable app that can grow with you.

A quality software development team will embrace testing as a tool to make sure that your product can make an impact, and customers will love using it — now and as you grow. 

Building a health & wellness app? Read our free guide, The Impact of the ‘World’s Largest Work-From-Home-Experiment,” to learn how you can be on the front lines of COVID-19 innovation. Download it here.

You want an app. Seems simple, right? But much like purchasing a car, there is no one-size fits all solution when it comes to mobile apps. Among all of the decisions you’ll need to make when building your app, from a technical perspective, the most important decision is what type of app will it be? And I’m not talking about iOS or Android (though those are also important decisions to make!). I’m talking about how will your app be built. Native? Hybrid? Web?

Read on to find out about the three different ways your app could take shape.

Native Apps

At Oak City Labs, we consider native apps to be our bread-and-butter. Native apps are built with a specific platform in mind – like iOS or Android. Users download these apps from the Apple App Store or Google Play Store. Native apps are capable of taking advantage of device features – like the camera, GPS, contacts, etc. – for use within the app. They can also employ push notifications and work with or without access to internet.

From a technical perspective, these apps have a codebase of either Swift/Objective-C for iOS and Java/Kotlin for Android and are built according to the standards set forth by Apple and Google (who also offer SDKs).

Side note: There is also such a thing as a cross-platform native app. You can read more about that here.


A web app is a completely different approach. The easiest way to differentiate a web app from a native app is that web apps aren’t downloaded from app stores. Web apps are built like a website would be with HTML, CSS and JavaScript and can be accessed with your phone’s mobile browser. They are quick and simple to develop, but don’t allow for the wider range of functionality that native apps do like push notifications, integration with the device camera, contacts, GPS and more.

Side note: There are also such things as progressive web apps. You can read more about those here.


So what’s left? A combination of the two types we’ve discussed already: hybrid apps.

As their name suggests, hybrid apps are part native app, part web app. You download these apps from an app store, but they are essentially just a wrapper (called a WebView) around a web app. That would be appealing if you want to spin up a quick minimum viable product (MVP), which is often simpler to do as a web app, but still would like to have users download your app from the app store. The pros: you’ll have access to analytics of app downloads and usage. The cons: performance is inferior to a native app and you’ll likely have to scrap everything and start fresh if you chose to move forward and expand the MVP into a full-fledged native app.

Our Recommendation

We weren’t kidding when we said there is no one-size fits all solution when it comes to building a mobile app. Our best recommendation if you’re just beginning the app development process is to partner with someone who can walk you through the specifics of each approach and guide you into the solution that makes the most sense for both your short term and long term goals. Sound like something you’d like more information on? We’d love to chat with you!

Like many tools, the Git version control system, through a handful of commands, provides the core functionality that most users need on a daily basis. In today’s post, I will touch on one that doesn’t seem to be used as often or, for many users, hasn’t been used at all.

Making One of Two

One of Git’s primary features, that was a huge draw for me when first exposed to it, is branching. Unlike many other systems, branching in Git is simple, fast, and the expected procedure to use for introducing new functionality, fixing bugs, and more.

If you are working alone in a repository, there usually isn’t much of a need to do more than simple branch and merge operations. However, Wwhen you add more contributors, however, managing the work can become a bit more involved, but still be easily maintained with the branch and merge commands.

A Common Occurrence

Let’s say you’ve created a new feature branch off the dev branch, done some work (with commits) locally in that branch, then see that someone else working in a different feature branch has merged their work into the dev branch.


You might continue work in your feature branch because the new code doesn’t affect what you’re implementing and vice versa. But often, you will find yourself wanting to incorporate changes and fixes into your current working branch.

At this point, most will reach for the merge command which will definitely do the job it’s intended to: incorporate changes from another branch into the current one. But if you find yourself (or other team members) doing this often, you can wind up with quite a few merge commits which, among other things, can make it a bit more difficult to understand the history of the project. Merge commits don’t go away when you eventually fold your feature branch back into dev.


While merging is a common workflow for handling these kinds of scenarios, another option is the rebase command.

The rebase command performs the same work as a merge operation, but in a way that results in a different historical view.

Using the scenario from above, how would things look if you reached for rebase instead of merge?

When you choose to rebase the changes in your feature branch onto those that have been committed in another branch (such as the dev branch your feature branch is based on), the command goes back to the common ancestor snapshot saving the feature branch commits to temporary files. It then switches to the branch you’re rebasing (dev), resetting HEAD to the most recent commit, then replaying those stored commits from your feature branch and reinstating the feature branch.

Now, a look at the history will appear to show that the work you’ve done in the feature branch came after the commits (done in parallel) in the dev branch. No merge commits will “litter” the history of either branch.


While this workflow may seem only useful for resulting in clean commit histories, Scott Chacon (Pro-Git) makes a good point that it also provides value when working in a repository that you don’t maintain. By rebasing your work on origin/dev, for example, the maintainer does not need to do any integration work to incorporate your changes; just a fast-forward merge.

The Golden Rule

There is absolutely at least one time when you will not want to employ the rebase command: when your commits have been published outside of your repository.

If you’ve pushed local commits to a remote, do not use the rebase command. It can cause lots of pain and suffering for your teammates (even though there are ways to work around it).

To rebase or not to rebase…

Most people are comfortable with just using the merge command for combining branch work. It’s a completely serviceable practice and, as they will often argue, shows the “true” history of the repository compared to rebasing.

I often reach for rebase when hot fixes or other commits that contain changes I’d like to have while working in a feature branch are made before I’m done. The feature branch will be clear of those merge commits letting me see a clear history of the work and will maintain that clarity when it is merged back into the destination branch.

At Oak City Labs, we’re working more and more with computer vision (CV) and image analysis, so it’s exciting to see how others are using CV to solve problems. Face ID from Apple has garnered a ton of recognition in the past few months as they attempt to use CV to solve the issue of mobile authentication.

FaceID is a technology that Apple shipped in the fall of 2017 as part of the iPhone X. The phone projects a constellation of 30,000 infrared dots onto your face and the user facing camera reads the location of those dots. The phone can use that positional information to create a 3D map with enough detail that it’s unique (with a one in a million chance of false positives).

FaceID replaces TouchID, a fingerprint based authentication technology that is fast and relatively mature. I’ve spoken with some folks who lament the loss of TouchID in favor of FaceID. They miss the ability to unlock a phone with only touch without having to ‘frame’ their face, so the phone gets a good look at it. Others say FaceID is too slow, or doesn’t work consistently enough, falling back to manual passcode entry. While FaceID might have some rough edges in this initial release, in the long term, FaceID will win out over TouchID.

TouchID was a great stepping stone, enabling much better security with relatively low friction. But it didn’t work for everyone. I know several people who aren’t able to consistently unlock a phone with TouchID. In my experience, they all have small hands and slim fingers that don’t seem to adequately cover the TouchID sensor. The other group with TouchID issues all have “end of the world” type cases on their phones. These big, bulky, indestructible cases promise to save your phone from the harsh reality of a concrete world. While they do an admirable job, they often make it physically difficult to place a finger fully on the TouchID sensor, rendering it useless. These are the worst kind of experiences for a technology like TouchID, where they train the user that it’s difficult to use and unreliable.

FaceID solves a lot of these issues. By not requiring physical contact, having the “right size” fingers isn’t an issue. Neither is encasing your phone in drop-proof armor as long as the camera can still see. Other issues with FaceID, like taking too long or having to ‘frame’ your face for the phone are just growing pains associated with an initial release. FaceID is usually fast enough not to notice now, and in a few years time, it will be fast enough to be unnoticeable. I also expect the cameras to continue to improve their field of view so FaceID is effective at wider angles. As the software is refined and the hardware evolves, FaceID will only improve.

FaceID brings something to the table that TouchID simply can’t — continuous authentication. That’s the idea that authentication isn’t something that happens once when you start a session, but something that happens continuously as long as you’re using the device. You see a bit of this on the iPhone X with notifications. When a new notification pops up on the screen, it doesn’t have real content. The phone shows a “New Email” pop up, but no content or who it’s from. When you, the phone’s owner, pick up the phone and look at it, FaceID verifies you and the notification changes to show who the email is from and the first bit of text. Imagine extending this to third party apps like 1Password. When you’re looking at the screen, the passwords might be automatically displayed, but when you look away or put the phone down, they’re obscured again. You could also imagine an online testing service that could use continuous authentication to ensure that you’re the one taking the test and not your buddy who’s much better at calculus. We’re scratching the surface of all new use cases for security and convenience with continuous authentication and I’m very excited to see where we’ll go next.

As FaceID becomes commonplace, we’ll see it adopted in many devices beyond phones and tablets. Because it doesn’t rely on physical contact, like TouchID, it’s easier to see it adapted to devices like AppleTV. In the large screen, 10 foot interface, FaceID could be the key to finally having a seamless multi-user experience. As I approach the TV, I’m identified and verified and I have access to all my content. Kids in the house might be identified in the same way and presented with only their kid friendly content. And if we really want to jump ahead, imagine FaceID for your car, where it unlocks and starts because it recognize you as it’s owner.

TouchID was an incredible innovation in making devices more secure with less burden on the user. FaceID is the next step in the evolution of strong security coupled with ease of use. As this technology becomes a staple of our digital world, we’ll see it applied to more and more niches. As we develop computer vision solutions at Oak City Labs, we’ll be considering how we can incorporate this kind of ingenuity as we solve problems for our clients. If you have a computer vision problem that you need help with, let us know! We’d love to speak to you!


During some recent discussions with clients, I noticed we tend to throw around the SDK acronym quite a bit. Today we’re going to simplify what an SDK is, share examples of SDKs and talk through how you might think about an SDK as a potential software product or product extension in the future.

What is an SDK exactly?

SDK stands for software development kit. An SDK is typically a 3rd party chunk of pre-written code. For example, in the majority of mobile apps, a user will need to login and most apps use social logins like Facebook, Twitter and Google. All three companies provide an SDK which a developer “drops” into the mobile application they’re building. That SDK allows the developer to quickly, easily make the right calls (via code) to Facebook for instance to authenticate the user (make sure the user is who they say they are).

Examples of SDKs you might hear used in mobile or even web applications:

Analytics and crash reporting

User Login/Authentication

Notifications, Engagement and Messaging



There are also SDKs available for news feeds, weather data, restaurant reservations, and more.

Potential Downside of SDK usage in mobile apps

SDKs are pretty powerful in terms of speeding up app development. They allow a development team to quickly put a new feature in place without building it from scratch. As always, with great power comes great responsibility. Occasionally, and it’s probably not happening to your mobile app, developers (or product owners) start to integrate multiple ad networks, authentication methods and analytics services. All of these integration points can slow the performance of the app down and also introduce complexity in the troubleshooting process. The SDK will need to be updated when a new version is released by the provider to continue to receive support from the provider. With any new release or change, that update can either introduce or fix bugs. SDKs also introduce a layer into your application that can make debugging (or bug fixing) more complicated. Often times the bug will be in the SDK but the app developers hands are tied waiting for the SDK owner to release an update. Ask yourself these questions before integrating a third party SDK:

  1. Does it provide value to the user of the mobile or software application?
  2. What if the owner of the SDK goes out of business or is acquired (ahem…anyone remember Parse?)
  3. Does it impact the performance and user experience of the app?
  4. Is there clear documentation and support around the SDK?
  5. What security risks will the SDK introduce?
  6. Is the SDK a critical component? If so, are you OK depending on a third party for bug fixes and support?

Building your own SDK

It might make sense for your business to build an SDK depending on your product and market. For example, one of our clients came to us with a connected device for motorcycles (think IoT – Internet of Things). We’ve built an SDK that handles the communication between another mobile application, an IoT platform and the hardware device itself. The SDK is a part of the product and necessary for device operations.

In the case of Facebook or Twitter, the SDK is a free extension of their product that allows the social network to grow and also acquire data on the users. Whenever a Facebook or Twitter SDK is integrated into an app, it potentially allows the app developer to access data about the user and also share that data back to the social network. Yes, if you have an app on your mobile phone, you are likely being tracked for advertising purposes.

Here are a few more examples of potential market opportunities for an SDK:

  1. Connected devices (IoT) where you might want other developers to also integrate your product. Something generic like bluetooth beacons are a great example.
  2. Products that provide a general utility. Analytics services, data services, or “platform” specific products that house data from your application. For example, you might build a platform product that handles user reviews for consumer products. You could provide an SDK that allows a developer to easily pass user reviews from the application to your review platform that centrally stores the user reviews. The platform could then provide analytics on the review data. In that scenario, you might need to gain as many reviews as possible for data analysis and would consider giving the SDKs to developers for free and then sell the data to product companies.
  3. Unique app features that would take a developer extensive time and resources to build on their own, for example unique maps or even making an app screenshot-proof!

SDKs present opportunities and challenges for mobile and software applications. A discerning developer will help you choose wisely and also help you understand if there are potential market opportunities to expand your product. Along with APIs, they can be a pretty powerful tool in the software developers arsenal. If you’d like to chat more about SDKs or any software development project, drop us a note!

Often, our daily challenges are solved with simple solutions instead of overly complex or “clever” ones that even the authors don’t understand a month later.

Not long ago (in a galaxy quite close to our own; okay, the same one), we ran into an issue where we needed to add activity indicators on some tableviews when getting information from a remote data source. At first, this seemed liked a simple case of showing the indicator when the call for data begins then hiding it in the completion handler when the data is returned.

We soon noticed that there were times when the activity indicator was being hidden while a search was still executing. The only code to hide the indicator was in the completion handler so, at first, it was puzzling.

The application’s data access layer often allows that a request for data be “cancellable.” There are times when a request might be in flight (e.g., an initial call for data when the tableview appears) and a user may tap into the search bar and start filtering by keying characters before a previous search has completed. When that happens, the previous search is cancelled, but the completion handler from that search still gets called.

As hiding of the activity indicator takes place in the completion block, this would occur even though another request was still being executed. We needed to prevent the indicator from being hidden until the last request was finished and ignore the other callbacks that would have hidden it prematurely.

Our first thought was to have an ivar that could be incremented when searches were made then decremented in the completion handler. When it reached zero, that would indicate that the last executing search had finished and then, and only then, would hiding of the indicator take place. It was a simple solution and worked as expected.

After considering the number of places this would be useful, we decided to create a struct that exposes two methods:

An internal counter goes up and down with calls to the corresponding method, and false is only ever returned if the value is zero.

Now, when a search is started, the increment method is invoked on the counter and the flag indicating that an update is occurring is set just prior to the call for data:

When the completion handler is called, the decrementWithValue() method is called on our counter and its result assigned to the flag (which also hides the activity indicator):

Now, if a search is cancelled for whatever reason and another search is still executing, the completion handler is still called, but the activity indicator remains visible.

This solution eliminated the need to go deeper into the data access layer and attempt a modification that may have resulted in unexpected behaviors and side effects.

At times, it’s tempting to create “clever” or “elegant” solutions to problems we’re dealing with. And sometimes, those are the things to do. But it’s also valuable to consider the “keep things simple” principle when you’re writing code. Your team (and maybe, your future self!) will appreciate it.

Today I’m going to discuss some pros and cons of Realm and Room for Android data persistence. Room was introduced at Google I/O 2017 as part of the Android Architecture Components. Realm is a mobile database solution that was launched for Android in May 2014 and has become a feature-rich choice for data persistence. While both serve a similar purpose, they are very different in implementation and their effectiveness may vary depending on your projects needs.

Room is just a layer over the native SQLite that comes stock with Android. As such there is a large amount of customizability in the queries (you write your queries in SQL and they are validated at compile time). However, Room also requires that relationships be created using foreign keys and the like, so complicated object graphs can be a bit of a pain to implement. Realm on the other hand requires no SQL knowledge. You do not have to write any SQL statements and object relationships are incredibly simple to implement. Referencing one object (or a list of them) from another object creates the relationship automatically.

Realm is a much larger library than Room because it includes a separate database. It adds somewhere around 3-4 MB to your app’s apk. Because Room is just a layer on top of SQLite, it only adds a few dozen KB to the APK. Room also contains far fewer methods if you are concerned about dex method limit.

Realm requires that objects not be passed between threads. Realm data objects are views into the data that respond to database changes so they are tied to whatever thread the Realm instance that they were retrieved from exists in (if that Realm instance is closed, any objects retrieved from it become invalid). In my experience this isn’t generally much of an issue if you are careful about it, but if you find yourself switching threads a lot you’ll have to create new Realm instances and re-query to get your objects. No such thread limitations exist for Room.

Room is officially supported by Google, so it should remain well supported and will likely have good community support. On the other hand, Realm has been around for a while (officially released about 4 years ago for Android) and has undergone tons of bug fixes and improvements and has an active community. Additionally, Realm supports iOS as well as Android, so developing for both platforms with virtually the same data persistence layer can allow for similar app architectures.

Both libraries support reactive queries, allowing you to subscribe to updates on a view of your data. Room achieves this using LiveData, another part of the Android Architecture Components, which can be linked to an app component (Activity, Fragment, etc.) and update intelligently based on the lifecycle of the component (i.e. not causing UI updates when an Activity is in the background). This is a nice feature to have out of the box and allows you to avoid keeping track of unsubscribing listeners in backgrounded app components. Realm objects, lists, and query results can all be directly subscribed to in order to monitor for changes, convenient features not entirely present in Room. Realm also has an additional library for an auto-updating Recyclerview adapter. While something similar isn’t too complicated to implement with LiveData, Realm’s library comes for free and works well.

Depending on your app’s data model complexity, APK size concerns, and personal experience/preference both Realm and Room are viable options for data persistence. Let us know in the comments which one you prefer.

At Oak City Labs, we rely heavily on unit testing in our quality software process. Unit testing is the safety net that lets us reliably improve existing code. Our testing suite double checks that modified code still behaves the way we expect. I’ve written before (here and here) about how we use dependency injection (DI) which makes unit testing easier. DI helps us wrap code we need to test in a special environment. By controlling the environment, we can make sure that the code being tested gives the correct output for a given set of conditions. This works very well for pieces of the application that we can encase in a layer that we control. Unfortunately, we can’t wrap everything.

Consider our API layer. This is the part of our application that talks to the server over the internet. It makes network calls, processes replies and handles errors like slow network responses or no network at all. In testing this code, we want it to behave as normally as possible for accurate testing, so we still want it to make API requests and interpret the results. At the same time, these are unit tests, so they should be fast and not depend on external resources. We don’t want to make requests to a real server on the internet. If that test failed, it wouldn’t be obvious if our code was broken, the server was down or the network cable was mistakenly unplugged. It’s important that our unit tests be self contained so when something fails, we know that a specific section of code has failed and we can fix it ASAP.

Back in the old days, before Swift, we wrote in Objective-C. Swift is a strongly typed language where Objective-C is weakly typed. While weak typing in Objective-C often gave a developer enough rope to hang themselves, it was flexible enough to do interesting things like easily mock pieces of software. Using mocks, fakes and stubs, you could (with some effort) replace pieces of the system software with substitute code that behaved differently.  We could use this to test our API code by changing the way the system network routines worked. Instead of actually contacting a server on the internet, a certain call just might always return a canned response. Our API code wouldn’t change, but when it asked the system to contact the server for new data, our mocked code would run instead and just return the contents of a local file. By building a library of expected calls and prepared responses, we could create a controlled environment to test all our API code.

Swift, on the other hand, brought us strong typing, which wiped out whole classes of bugs and insured that objects were always the type we expected. We paid for this safety with the loss of the ability to easily mock, fake or stub things. Someday, Swift might gain new features that make this possible (maybe even easy) but for now, this is difficult to do with any efficiency. So, we need a new approach for testing Swift API code.

Like we said earlier, we don’t want to use external network resources to test our code because too many things are out of our control. But what if the test server were running on the development machine? In fact, what if the test server were running inside our application? Enter two open source projects — Ambassador and Embassy from the fine folks at  Embassy is a lightweight, dependency free web server written in Swift. Ambassador is a web framework that sits on top of Embassy and makes it easy to write a mock API server.  

In this new approach to testing our API layer, we’ll write some Ambassador code to define our mock endpoints. We’ll declare what URL path they’ll respond to and what response the endpoint will return. Now, inside our unit test, we’ll fire up the mock server and run it on a background thread.  Since you’re controlling both the client and server side of the request, you can make asserts on both ends of the exchange. On the server side, you can validate the data submitted by the client. On the client side, you can ensure that the response from the server was interpreted correctly. Ambassador has some nice conveniences as well for working with JSON data, introducing delayed responses from the server and other common testings needs.

In order to use our freshly built API mock server, all you need to do is change the server name used by the API layer. This is important because we don’t want to make significant changes to our code in order to test it. We want to test as close as we can to production code. By switching our base URL from “” to “http://localhost:8080”, we can test all our network requests with no external dependencies. Since we’re using dependency injection, this change is very simple to implement in our unit testing routines.

The move from Objective-C to Swift has allowed us to write cleaner and safer code, but the price we pay is the loss of the fast and loose, über permissive Objective-C runtime environment. Fast and loose always caused more problems than it solved, so I’m happy to see it go. A few of our existing solutions have gone with it, but with a bit of ingenuity, we can move forward with better and safer new solutions.


Subscribe for the latest updates

Where problems get solved.

© 2020 Oak City Labs | A Well Refined Website