Introduction
Testing is crucial in software development, especially for the several custom plugins I’m building for Obsidian. I rely on Jest, a testing tool that incorporates Mocks, to simulate Obsidian’s API. This approach is key to making tests isolated and dependable. In this post, I’ll show you how to create a flexible and scalable mock framework for the Obsidian API using Jest, tailored for developing multiple custom plugins.
Why Modular Mocks?
As your plugin grows, so does the complexity of your testing environment. A modular approach to mocking allows you to:
- Maintain Separation of Concerns: Each mock file focuses on a specific aspect of the API, making it easier to manage and update.
- Improve Scalability: Adding new mocks or updating existing ones is straightforward, even as your project expands.
- Ensure Consistency: A centralized
index.js
file aggregates all your mocks, providing a single source of truth for your tests.
Setting Up Your Project Structure
Start by organizing your project directory to accommodate the modular mocks:
project-root/
├── __mocks__/
│ ├── App.js
│ ├── TFile.js
│ ├── Modal.js
│ ├── Plugin.js
│ ├── Vault.js
│ ├── Workspace.js
│ ├── MarkdownView.js
│ ├── Setting.js
│ ├── StatusBarItem.js
│ └── index.js
├── src/
├── tests/
└── package.json
Creating the Individual Mock Files
Each file in the __mocks__
directory represents a component of the Obsidian API. Here’s a brief look at how to structure these files:
-
__mocks__/TFile.js
: Represents files within Obsidian’s vault.class TFile { constructor(path) { this.path = path; } } module.exports = TFile;
-
__mocks__/Modal.js
: Used for creating custom modals.class Modal { open() {} close() {} contentEl = { createEl: jest.fn(), empty: jest.fn(), }; } module.exports = Modal;
-
__mocks__/Plugin.js
: Represents the core plugin functionality.class Plugin { addSettingTab() {} registerEvent() {} addStatusBarItem() {} loadData() { return Promise.resolve(null); } saveData() { return Promise.resolve(); } } module.exports = Plugin;
Centralizing Mocks with index.js
To make your mocks easily accessible, create an index.js
file in the __mocks__
directory that consolidates all individual mocks:
const App = require('./App');
const TFile = require('./TFile');
const Modal = require('./Modal');
const Plugin = require('./Plugin');
const Vault = require('./Vault');
const Workspace = require('./Workspace');
const MarkdownView = require('./MarkdownView');
const Setting = require('./Setting');
const StatusBarItem = require('./StatusBarItem');
module.exports = {
App,
TFile,
Modal,
Plugin,
Vault,
Workspace,
MarkdownView,
Setting,
StatusBarItem,
};
Using the Mocks in Your Tests
With the mocks in place, you can import them directly in your test files. For example:
import { Plugin, TFile, Modal } from '../__mocks__/index.js';
import ParaTaxonomyClassifierPlugin from '../src/main';
describe('ParaTaxonomyClassifierPlugin', () => {
let plugin;
beforeEach(() => {
plugin = new ParaTaxonomyClassifierPlugin();
plugin.settings = DEFAULT_SETTINGS;
});
it('should do something with TFile and Modal', () => {
const mockFile = new TFile('path/to/file.md');
const modal = new Modal();
plugin.handleFileSave(mockFile);
expect(modal.open).toHaveBeenCalled();
});
});
Conclusion
By structuring your mocks in a modular way, you gain control over your testing environment, ensuring that your tests are both reliable and maintainable. This approach is not only scalable but also simplifies the process of managing complex dependencies, making your development process smoother and more efficient.
Call to Action
If you’re developing Obsidian plugins and haven’t tried modular mocks yet, give this setup a try! It will help keep your tests clean, organized, and easy to manage as your project grows. Happy coding!
Sources
Separation of Concerns
- Parnas, D. L. (1972). On the criteria to be used in decomposing systems into modules. Communications of the ACM, 15(12), 1053-1058. https://doi.org/10.1145/361230.361257
Scalability
- De Moura, L., & Bjørner, N. (2008). Z3: An efficient SMT solver. In Tools and Algorithms for the Construction and Analysis of Systems (pp. 337-340). Springer. https://doi.org/10.1007/978-3-540-78800-3_24
- Lee, E. A. (2008). Cyber-physical systems – Are computing foundations adequate? Position Paper for NSF Workshop on Cyber-Physical Systems: Research Opportunities and Challenges. Retrieved from https://www.eecs.berkeley.edu/~lee/lee-cyber-physical-systems.pdf
Consistency
- Lamport, L. (1978). Time, clocks, and the ordering of events in a distributed system. Communications of the ACM, 21(7), 558-565. https://doi.org/10.1145/359230.359256
- Brewer, E. A. (2000). Towards robust distributed systems. In Proceedings of the 19th Annual ACM Symposium on Principles of Distributed Computing (pp. 7-8). https://doi.org/10.1145/347090.347101
General Resources
- Bass, L., Clements, P., & Kazman, R. (2012). Software Architecture in Practice (3rd ed.). Addison-Wesley.
- Kruchten, P. (2004). The Rational Unified Process: An Introduction (3rd ed.). Addison-Wesley.
Post Disclaimer
The information contained on this post is my opinion, and mine alone (with the occasional voice of friend). It does not represent the opinions of any clients or employers.