The Mockingbird package provides tools for locally overriding the definitions of Wolfram Language functions and symbols. This general functionality is useful for multiple purposes, including interactive development and debugging, but one particularly strong application is the writing of software tests involving a "mocking" or "stubbing" pattern.
patterns – originate from the object-oriented programming (OOP) world, and both involve substituting special versions of objects that mimic the behavior of real objects for testing. This is valuable both conceptually, in allowing the scope of unit tests to be restricted to only the functionality they're intended to test, and practically, in enabling unit and integration tests that avoid calling the real implementation of methods or APIs whose execution may be expensive – in time, computational power, or even in money. Additionally, mocking/stubbing makes it easy to simulate failure modes that may be difficult to elicit on demand from a real application.
The original OOP sense of "mock/stub objects" does not readily lend itself towards use in the Wolfram Language, as the language does not offer a strict object-oriented paradigm. However, the Wolfram Language does offer powerful tools for locally modifying the behavior of function and symbol definitions. Mockingbird provides a wrapper around this functionality that can be used in a similar spirit, and to more or less the same end, when writing software tests in the Wolfram Language.
The distinction between mocking and stubbing is fairly subtle when viewed without the OOP lens. This rest of this document, and that of Mockingbird's documentation, will generally use the term "mock", although Mockingbird can also be used to implement Wolfram Language versions of stubs and other "test double"-like patterns.
Fundamentals of Mock Evaluation
This tutorial will focus mostly on the application of Mockingbird to the specific use case of software testing, but it is useful to first give a brief overview of the basic concepts and operation of the package.
) accessible via an HTTP API. Bear in mind, though, that Mockingbird is not limited to such applications – mocking I/O operations such as database queries or filesystem access is also feasible. Indeed, the behavior of most any built-in or custom Wolfram Language function can be mocked.
The mocked version of the API call is far faster than sending a real HTTP request.
Validating Request Parameters
Mock the /deck/new/shuffle API endpoint, asserting that the request includes the expected deck count as an HTTP query parameter.
Testing Failure Behavior
Testing that code behaves as expected during failure cases is an important yet oft-neglected element of software testing. Mockingbird makes it easy to simulate failure modes in practically any part of an application or an external service that it depends on.
Constructs for validating properties of HTTPRequest and other "object"-like expressions.
This tutorial has covered an example application of Mockingbird to testing a Wolfram Language client for an HTTP API. As noted in the introduction, Mockingbird is a general tool for creating and reusing local definitions, and is not limited to the particular use patterns demonstrated in this document.
This section will describe some specific features, patterns, and potential use cases beyond those demonstrated previously.
Contrary to the standard behavior of Wolfram Language definitions, Mockingbird mock definitions normally do not recurse – i.e. a given definition can only appear once on the evaluation stack. This behavior is useful for "spying" patterns that involve inspecting the arguments passed to a function while still ultimately calling the original or built-in implementation of the function.
Catching Fall-Through Evaluations
Mock definitions precede, but do not supplant, existing definitions for a given symbol. If an invocation of a mocked function does not match any mock definition patterns, the function's original definition will be called.
The mock definition intended for sendRequest does not catch URLExecute called with two arguments, as done in sendRequestWithParameter.
Saving and Reusing Mock Definitions
Maintaining State in Mock Definitions
It is permissible for mock definitions to access and modify global state, or state otherwise outside of their local scope.
It is probably advisable to exercise discretion in applying this pattern so as to avoid writing tests that are excessive in imitating application behavior irrelevant to the part of the system under test. In particular, mock definitions should use global state with extreme care.