To run end-to-end tests on your application, you need to integrate the C++ library, as well as using the JavaScript package.
-
Add FocusriteGroup/juce-end-to-end as a submodule to your project (for example, to
lib/juce-end-to-end) -
In your CMakeLists.txt, add the directory (after JUCE has been made available)
add_subdirectory (lib/juce-end-to-end)
-
Link your target with the
focusrite-e2e::focusrite-e2etargettarget_link_libraries (MyApp PRIVATE focusrite-e2e::focusrite-e2e)
-
Add the following to your CMake project to fetch juce-end-to-end, swapping the version for the version that you wish to use. This should be done after JUCE has been made available.
include (FetchContent) FetchContent_Declare (juce-end-to-end GIT_REPOSITORY https://github.com/FocusriteGroup/juce-end-to-end GIT_TAG v0.0.15) FetchContent_MakeAvailable (juce)
-
Link your target with the
focusrite-e2e::focusrite-e2etargettarget_link_libraries (MyApp PRIVATE focusrite-e2e::focusrite-e2e)
- Add FocusriteGroup/juce-end-to-end as a submodule to your project
- Add the enclosed
includefolder to your include path - Compile all sources in the
sourcesfolder - Ensure
juce/modulesis available on your include path - Ensure JUCE is linked with your executable
All you need to do to make your application testable is create a
focusrite::e2e::TestCentre that has the same lifecycle as your application.
For example:
#include <focusrite/e2e/TestCentre.h>
class Application : public JUCEApplication
{
public:
void initialise (const juce::String &) override
{
testCentre = focusrite::e2e::TestCentre::create ();
}
void shutdown () override
{
testCentre.reset ();
}
private:
std::unique_ptr <focusrite::e2e::TestCentre> testCentre;
}If you wish to reference a Component by ID, you can set the component ID on the component:
myComponent.setComponentID ("my-component-id");If you happen to be using the component ID for something else, you can set the test ID as follows:
#include <focusrite/e2e/ComponentSearch.h>
// ...
focusrite::e2e::ComponentSearch::setTestId ("my-component-id");If you wish to install a custom request handler in addition to the default one, you can do so as follows:
#include <focusrite/e2e/CommandHandler>
class MyCommandHandler : public focusrite::e2e::CommandHandler
{
public:
std::optional<focusrite::e2e::Response> process (const focusrite::e2e::Command & command) override
{
if (command.getType () == "my-custom-type-1")
return generateResponse1 (command);
else if (command.getType () == "my-custom-type-2")
return generateResponse2 (command);
return std::nullopt;
}
};
MyCommandHandler commandHandler;
testCentre->addCommandHandler (commandHandler);You can also send custom events at any time, without needing to wait for a request:
const auto event = focusrite::e2e::Event ("something-happened")
.withParameter("count", 5));
testCentre->sendEvent (event);The JavaScript library provides utilities to start the application, send it commands and query its state. You can use it with any testing/assertion library (e.g. Jest, Mocha). The examples below are written in TypeScript using Jest.
-
Install Node (you can use a Node installer, a system package manager, or a Node version manager)
-
Initialise an npm package at the root of your repository using
npm init
-
Install the library using npm
npm install @focusritegroup/juce-end-to-end
-
Install a test framework (e.g. Jest)
-
In your test setup, before each test, create an
AppConnection(passing it the path to your built application) and wait for it to launch. You can pass the app extra arguments or set environment variables if you need to put it into special state.import {AppConnection} from '@focusritegroup/juce-end-to-end'; describe('My app tests', () => { let appConnection: AppConnection; beforeEach(async () => { appConnection = new AppConnection({appPath: 'path/to/app/binary'}); await appConnection.launch(); }); }
-
After each test, quit the app and wait for it to shut down:
afterEach(async () => { await appConnection.quit(); });
-
During each test, you can send the application commands to query the state of the user interface. Here is a simple example:
it('Increments using the increment button', async () => { const valueBefore = await appConnection.getComponentText('value-label'); expect(valueBefore).toEqual('0'); await appConnection.clickComponent('increment-button'); const valueAfter = await appConnection.getComponentText('value-label'); expect(valueAfter).toEqual('1'); });
See the documentation for AppConnection for the full range of supported
commands and responses. If you need to extend this, you can send custom commands,
as long as it can be serialised to JSON using
appConnection.sendCommand (myCustomCommand);