Skip to content

Building an AppCode plugin

Motivation

Test doubles (sometimes referred to as mocks) are essential when writing unit tests. In Objective-C we had tools such as OCMock to ease this process. But Swift is still in its infancy and is lacking a full implementation of reflection and ambient context dependency injection (method swizzling) meaning tools like OCMock cannot work with pure Swift.

In Swift, test doubles must be written manually. The pattern I follow when writing a test double is something like this:

This structure enables the mock objects to be reusable between tests. These mocks can also be expanded catch invoked method parameters and return stubbed objects if required.

Although there’s not much boiler plate in this example, protocols and method signatures can become more complex and are a pain to maintain when refactoring.

This problem can be solved by writing a plugin for AppCode* to ensure I don’t have to write this boiler plate ever again! So let’s get started.

* if you haven’t tried AppCode yet, you have to give it a try. Yes, there are new things to learn with a new IDE but, trust me, AppCode will massively speed up your workflow. If you’ve tried AppCode in the past but were put off by the lack of Swift support, then it’s time to come back. It is now a joy to write Swift and more features are being added all the time.

Prerequisites

  • Download AppCode.
  • Download the community edition of IntelliJ IDEA (free). AppCode, like all other IDEs in the JetBrains family, is written in Java.
  • Ensure you have the correct JDK installed. At the time of writing AppCode uses JDK 8.

Creating a plugin project

Open IntelliJ and create a new IntelliJ Platform Plugin project. By default, this template will create a plugin using the IntelliJ IDEA SDK but we want to write a Swift specific plugin so must point to the AppCode SDK.

Click the New button to add a new Project SDK and open the AppCode.app/Contents directory.

If you use JetBrains Toolbox to install AppCode then it is found here: ~/Library/Application Support/JetBrains/Toolbox/apps.

1 New project

Create the project keeping all other options to their default.

Before we get started with any code there is an extra step to add the JDK to the class path of this SDK. Choose File -> Project Structure  and click on SDKs. You should see the AppCode SDK. If you click on the Classpath tab you will see all the imported JAR files. Add all the JAR files from the JDK in the following directories: /Library/Java/JavaVirtualMachines/<insert-jdk-version>.jdk/Contents/Home/jre/lib , /Library/Java/JavaVirtualMachines/<insert-jdk-version>.jdk/Contents/Home/jre/lib/ext , and /Library/Java/JavaVirtualMachines/<insert-jdk-version>.jdk/Contents/Home/lib .

1.a java classpath

Click OK and we’re ready to write some code.

Creating an intention

There are lots of different ways you can extend AppCode. If you’re interested, see Main Types of Plugins and Plugin Tutorials.

We’re going to create an intention action. These can be executed quickly and easily within the context of the editor.

Let’s start by adding a class for our intention. Our class will extend PsiElementBaseIntentionAction  and implement the IntentionAction  interface. Implement all the interface methods and override the getText()  method from the base class.

 

isAvailable() – we can control when the user can execute this action. For now we will return true so the intention is always available.
invoke()  – called when the user executes our intention. We will use this method to generate our test double code.
getText()  – the text that is displayed to the user. I’ve named the intention ‘Generate mock’.

Next we need to tell AppCode what actions our plugin offers and how to execute them. Open resources/META-INF/plugin.xml  and add the following XML. Be sure to change the package of the class if yours differs from mine.

Running the plugin

We are now ready to run the plugin. Click on the debug icon. It will build the plugin and launch AppCode.

4 run plugin

Let’s create a new Swift project to test our plugin.

5 Create test project

Create a protocol with a simple method.

In our test target, let’s create a mock class which implements that protocol

We can test our intention plugin by pressing alt+enter. Our Generate mock intention appears! 🎉

Notice how the intention appears wherever the caret is.

8 Demo intention

Improving isAvailable()

I would only like the intention to appear when the caret is inside a class but since isAvailable()  always returns true, the intention is available even when outside a class where it could not be executed.

9 Intention is everywhere

Let’s change isAvailable()  to only allow AppCode to show the intention when the caret is inside a class declaration.

Debug the plugin again and you will see the intention is no longer available unless the caret is within a class scope. 🎉

PSI

“A PSI (Program Structure Interface) file is the root of a structure representing the contents of a file as a hierarchy of elements in a particular programming language.”

The PSI is how AppCode understands the structure of the code. With the plugin running in AppCode open AppCode -> Preferences  and click on Plugins -> Browse Repositories  and search for PsiViewer. Install that plugin and restart AppCode.

10 psi viewer

In AppCode open View -> Tool Windows -> PsiViewer . We can use this window to inspect the underlying code structure of our Swift file.

11 inspect code psi

This is how I found out how to write the code for isAvailable() . The PsiElement  argument is the element under the caret. The isAvailable()  method checks to see if it has a parent of SwiftClassDeclaration  type and if so makes the intention available.

Implementing the mock generator

Generating the code for the mock class can be broken down into the following steps:

  1. Delete any existing methods in the mock class.
  2. Find the protocol that the mock class implements.
  3. Resolve the protocol and find its methods.
  4. Add those methods to the mock class.
  5. Add an ‘invoked’ property for each method.
  6. Set those properties to true when a method has been invoked.

 

Run the plugin again and you can see that the methods and variables are now generated automatically. We can even add more methods to the protocol and it will generate the mock code instantly. 🎉

mock-generator

There are lots of topics covered in the above implementation including the visitor pattern, showing errors, and more advanced PSI. If you become stuck writing your own plugin lots of detail can be found here and an active community base here. Good luck!

Subscribe now to hear more about AppCode tips and tricks

Published inAppCodeiOS

Be First to Comment

Leave a Reply