diff --git a/test/ipa/ipa_interface_test.cpp b/test/ipa/ipa_interface_test.cpp
new file mode 100644
index 000000000000..171e572461a5
--- /dev/null
+++ b/test/ipa/ipa_interface_test.cpp
@@ -0,0 +1,142 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2019, Google Inc.
+ *
+ * ipa_interface_test.cpp - Test the IPA interface
+ */
+
+#include <fcntl.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <iostream>
+
+#include <libcamera/event_dispatcher.h>
+#include <libcamera/event_notifier.h>
+#include <libcamera/timer.h>
+
+#include <ipa/ipa_vimc.h>
+
+#include "device_enumerator.h"
+#include "ipa_manager.h"
+#include "ipa_module.h"
+#include "pipeline_handler.h"
+#include "test.h"
+#include "thread.h"
+
+using namespace std;
+using namespace libcamera;
+
+class IPAInterfaceTest : public Test, public Object
+{
+public:
+	IPAInterfaceTest()
+		: trace_(IPAOperationNone), notifier_(nullptr), fd_(-1)
+	{
+	}
+
+	~IPAInterfaceTest()
+	{
+		delete notifier_;
+	}
+
+protected:
+	int init() override
+	{
+		/* Create a pipeline handler for vimc. */
+		std::vector<PipelineHandlerFactory *> &factories =
+			PipelineHandlerFactory::factories();
+		for (PipelineHandlerFactory *factory : factories) {
+			if (factory->name() == "PipelineHandlerVimc") {
+				pipe_ = factory->create(nullptr);
+				break;
+			}
+		}
+
+		if (!pipe_) {
+			cerr << "Vimc pipeline not found" << endl;
+			return TestPass;
+		}
+
+		/* Create and open the communication FIFO. */
+		int ret = mkfifo(VIMC_IPA_FIFO_PATH, S_IRUSR | S_IWUSR);
+		if (ret) {
+			ret = errno;
+			cerr << "Failed to create IPA test FIFO at '"
+			     << VIMC_IPA_FIFO_PATH << "': " << strerror(ret)
+			     << endl;
+			return TestFail;
+		}
+
+		ret = open(VIMC_IPA_FIFO_PATH, O_RDONLY | O_NONBLOCK);
+		if (ret < 0) {
+			ret = errno;
+			cerr << "Failed to open IPA test FIFO at '"
+			     << VIMC_IPA_FIFO_PATH << "': " << strerror(ret)
+			     << endl;
+			unlink(VIMC_IPA_FIFO_PATH);
+			return TestFail;
+		}
+		fd_ = ret;
+
+		notifier_ = new EventNotifier(fd_, EventNotifier::Read, this);
+		notifier_->activated.connect(this, &IPAInterfaceTest::readTrace);
+
+		return TestPass;
+	}
+
+	int run() override
+	{
+		EventDispatcher *dispatcher = thread()->eventDispatcher();
+		Timer timer;
+
+		ipa_ = IPAManager::instance()->createIPA(pipe_.get(), 0, 0);
+		if (!ipa_) {
+			cerr << "Failed to create VIMC IPA interface" << endl;
+			return TestFail;
+		}
+
+		/* Test initialization of IPA module. */
+		ipa_->init();
+		timer.start(1000);
+		while (timer.isRunning() && trace_ != IPAOperationInit)
+			dispatcher->processEvents();
+
+		if (trace_ != IPAOperationInit) {
+			cerr << "Failed to test IPA initialization sequence"
+			     << endl;
+			return TestFail;
+		}
+
+		return TestPass;
+	}
+
+	void cleanup() override
+	{
+		close(fd_);
+		unlink(VIMC_IPA_FIFO_PATH);
+	}
+
+private:
+	void readTrace(EventNotifier *notifier)
+	{
+		ssize_t s = read(notifier->fd(), &trace_, sizeof(trace_));
+		if (s < 0) {
+			int ret = errno;
+			cerr << "Failed to read from IPA test FIFO at '"
+			     << VIMC_IPA_FIFO_PATH << "': " << strerror(ret)
+			     << endl;
+			trace_ = IPAOperationNone;
+		}
+	}
+
+	std::shared_ptr<PipelineHandler> pipe_;
+	std::unique_ptr<IPAInterface> ipa_;
+	enum IPAOperationCode trace_;
+	EventNotifier *notifier_;
+	int fd_;
+};
+
+TEST_REGISTER(IPAInterfaceTest)
diff --git a/test/ipa/meson.build b/test/ipa/meson.build
index e174671d05e1..c501fcf99aed 100644
--- a/test/ipa/meson.build
+++ b/test/ipa/meson.build
@@ -1,5 +1,6 @@
 ipa_test = [
-    ['ipa_module_test', 'ipa_module_test.cpp'],
+    ['ipa_module_test',     'ipa_module_test.cpp'],
+    ['ipa_interface_test',  'ipa_interface_test.cpp'],
 ]

 foreach t : ipa_test
