diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 2315735..cef933f 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -38,7 +38,7 @@ jobs:
           -DCMAKE_PREFIX_PATH=$CONDA_PREFIX \
           -DCMAKE_INSTALL_PREFIX=$CONDA_PREFIX \
           -DCMAKE_BUILD_TYPE=Release \
-          -DDOWNLOAD_GTEST=ON
+          -DXVEGA_BUILD_TESTS=ON
       working-directory: build
 
     - name: Build
@@ -75,7 +75,7 @@ jobs:
           -DCMAKE_PREFIX_PATH="%CONDA_PREFIX%\Library" ^
           -DCMAKE_INSTALL_PREFIX=%CONDA_PREFIX% ^
           -DCMAKE_BUILD_TYPE=Release ^
-          -DDOWNLOAD_GTEST=ON
+          -DXVEGA_BUILD_TESTS=ON
       working-directory: build
 
     - name: Build
diff --git a/CMakeLists.txt b/CMakeLists.txt
index c9a5c6a..203a2bb 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -45,6 +45,8 @@ OPTION(XVEGA_DISABLE_TUNE_GENERIC "disable -mtune=generic flag" OFF)
 OPTION(XVEGA_BUILD_STATIC "Build xvega static library" ON)
 OPTION(XVEGA_BUILD_SHARED "Split xvega build into executable and library" ON)
 
+OPTION(XVEGA_BUILD_TESTS "xvega test suite" OFF)
+
 # Dependencies
 # ============
 
@@ -456,14 +458,7 @@ endif ()
 # Tests
 # =====
 
-OPTION(BUILD_TESTS "xvega test suite" OFF)
-OPTION(DOWNLOAD_GTEST "build gtest from downloaded sources" OFF)
-
-if(DOWNLOAD_GTEST OR GTEST_SRC_DIR)
-    set(BUILD_TESTS ON)
-endif()
-
-if(BUILD_TESTS)
+if(XVEGA_BUILD_TESTS)
     add_subdirectory(test)
 endif()
 
diff --git a/environment-dev.yml b/environment-dev.yml
index 2a00427..1828543 100644
--- a/environment-dev.yml
+++ b/environment-dev.yml
@@ -10,4 +10,5 @@ dependencies:
   - xtl=0.7.0
   - xproperty=0.11.0
   - nlohmann_json=3.11.2
-
+# Test dependencies
+  - doctest
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index b10aad4..55bf26b 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -67,44 +67,9 @@ else()
   message(FATAL_ERROR "Unsupported compiler: ${CMAKE_CXX_COMPILER_ID}")
 endif()
 
-if(DOWNLOAD_GTEST OR GTEST_SRC_DIR)
-    if(DOWNLOAD_GTEST)
-        # Download and unpack googletest at configure time
-        configure_file(downloadGTest.cmake.in googletest-download/CMakeLists.txt)
-    else()
-        # Copy local source of googletest at configure time
-        configure_file(copyGTest.cmake.in googletest-download/CMakeLists.txt)
-    endif()
-    execute_process(COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" .
-                    RESULT_VARIABLE result
-                    WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/googletest-download )
-    if(result)
-        message(FATAL_ERROR "CMake step for googletest failed: ${result}")
-    endif()
-    execute_process(COMMAND ${CMAKE_COMMAND} --build .
-                    RESULT_VARIABLE result
-                    WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/googletest-download )
-    if(result)
-        message(FATAL_ERROR "Build step for googletest failed: ${result}")
-    endif()
-
-    set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
-
-    # Add googletest directly to our build. This defines
-    # the gtest and gtest_main targets.
-    add_subdirectory(${CMAKE_CURRENT_BINARY_DIR}/googletest-src
-                     ${CMAKE_CURRENT_BINARY_DIR}/googletest-build EXCLUDE_FROM_ALL)
-
-    set(GTEST_INCLUDE_DIRS "${gtest_SOURCE_DIR}/include")
-    set(GTEST_BOTH_LIBRARIES gtest_main gtest)
-else()
-    find_package(GTest REQUIRED)
-endif()
-
+find_package(doctest REQUIRED)
 find_package(Threads)
 
-include_directories(${GTEST_INCLUDE_DIRS} SYSTEM)
-
 set(XVEGA_TESTS
     main.cpp
     test_marks.cpp
@@ -118,12 +83,9 @@ set(XVEGA_TESTS
 
 foreach(filename IN LISTS XVEGA_TESTS)
     string(REPLACE ".cpp" "" targetname ${filename})
-    add_executable(${targetname} ${filename} ${XVEGA_HEADERS})
-    if(DOWNLOAD_GTEST OR GTEST_SRC_DIR)
-        add_dependencies(${targetname} gtest_main)
-    endif()
+    add_executable(${targetname} ${filename} main.cpp ${XVEGA_HEADERS})
     target_include_directories(${targetname} PRIVATE ${XVEGA_INCLUDE_DIR})
-    target_link_libraries(${targetname} PRIVATE xvega ${GTEST_BOTH_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT})
+    target_link_libraries(${targetname} PRIVATE xvega doctest::doctest Threads::Threads)
     add_custom_target(
         x${targetname}
         COMMAND ${targetname}
@@ -132,11 +94,7 @@ endforeach()
 
 add_executable(test_xvega_lib ${XVEGA_TESTS} ${XVEGA_HEADERS})
 
-if(DOWNLOAD_GTEST OR GTEST_SRC_DIR)
-    add_dependencies(test_xvega_lib gtest_main)
-endif()
-
 target_include_directories(test_xvega_lib PRIVATE ${XVEGA_INCLUDE_DIR})
-target_link_libraries(test_xvega_lib PRIVATE xvega ${GTEST_BOTH_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT})
+target_link_libraries(test_xvega_lib PRIVATE xvega doctest::doctest Threads::Threads)
 
 add_custom_target(xtest COMMAND test_xvega_lib DEPENDS test_xvega_lib)
diff --git a/test/copyGTest.cmake.in b/test/copyGTest.cmake.in
deleted file mode 100644
index 46080b1..0000000
--- a/test/copyGTest.cmake.in
+++ /dev/null
@@ -1,20 +0,0 @@
-# Copyright (c) 2020, QuantStack and XVega Contributors
-#
-# Distributed under the terms of the BSD 3-Clause License.
-#
-# The full license is in the file LICENSE, distributed with this software.
-
-cmake_minimum_required(VERSION 2.8.2)
-
-project(googletest-download NONE)
-
-include(ExternalProject)
-ExternalProject_Add(googletest
-    URL               "${GTEST_SRC_DIR}"
-    SOURCE_DIR        "${CMAKE_CURRENT_BINARY_DIR}/googletest-src"
-    BINARY_DIR        "${CMAKE_CURRENT_BINARY_DIR}/googletest-build"
-    CONFIGURE_COMMAND ""
-    BUILD_COMMAND     ""
-    INSTALL_COMMAND   ""
-    TEST_COMMAND      ""
-)
diff --git a/test/downloadGTest.cmake.in b/test/downloadGTest.cmake.in
deleted file mode 100644
index 239225d..0000000
--- a/test/downloadGTest.cmake.in
+++ /dev/null
@@ -1,21 +0,0 @@
-# Copyright (c) 2020, QuantStack and XVega Contributors
-#
-# Distributed under the terms of the BSD 3-Clause License.
-#
-# The full license is in the file LICENSE, distributed with this software.
-
-cmake_minimum_required(VERSION 2.8.2)
-
-project(googletest-download NONE)
-
-include(ExternalProject)
-ExternalProject_Add(googletest
-    GIT_REPOSITORY    https://github.com/JohanMabille/googletest.git
-    GIT_TAG           warnings
-    SOURCE_DIR        "${CMAKE_CURRENT_BINARY_DIR}/googletest-src"
-    BINARY_DIR        "${CMAKE_CURRENT_BINARY_DIR}/googletest-build"
-    CONFIGURE_COMMAND ""
-    BUILD_COMMAND     ""
-    INSTALL_COMMAND   ""
-    TEST_COMMAND      ""
-)
diff --git a/test/main.cpp b/test/main.cpp
index 15a2fb9..9522fa7 100644
--- a/test/main.cpp
+++ b/test/main.cpp
@@ -1,7 +1,2 @@
-#include <gtest/gtest.h>
-
-int main(int argc, char **argv) 
-{
-    ::testing::InitGoogleTest(&argc, argv);
-    return RUN_ALL_TESTS();
-}
\ No newline at end of file
+#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
+#include "doctest/doctest.h"
diff --git a/test/test_configurations.cpp b/test/test_configurations.cpp
index ee328d4..8d94502 100644
--- a/test/test_configurations.cpp
+++ b/test/test_configurations.cpp
@@ -1,9 +1,11 @@
-#include <gtest/gtest.h>
+#include "doctest/doctest.h"
 #include <xvega/xvega.hpp>
 
 using namespace xv;
 
-TEST(JsonSpecOutput, DoubleGlobalConfig)
+TEST_SUITE("JsonSpecOutput")
+{
+TEST_CASE("DoubleGlobalConfig")
 {
     auto df = url_data().url("https://vega.github.io/vega-datasets/data/cars.json");
     auto mp = mark_point();
@@ -32,5 +34,6 @@ TEST(JsonSpecOutput, DoubleGlobalConfig)
                     "mark": {"type": "point"},
                     "width": 400
                 })"_json;
-    ASSERT_EQ(expected, result["application/vnd.vegalite.v3+json"]);
+    REQUIRE_EQ(expected, result["application/vnd.vegalite.v3+json"]);
+}
 }
diff --git a/test/test_data.cpp b/test/test_data.cpp
index 8541ffa..eeccafb 100644
--- a/test/test_data.cpp
+++ b/test/test_data.cpp
@@ -1,9 +1,11 @@
-#include <gtest/gtest.h>
+#include "doctest/doctest.h"
 #include <xvega/xvega.hpp>
 
 using namespace xv;
 
-TEST(JsonSpecOutput, UrlData)
+TEST_SUITE("JsonSpecOutput")
+{
+TEST_CASE("UrlData")
 {
     auto df = url_data().url("https://vega.github.io/vega-datasets/data/cars.json");
     auto fig = Chart().data(df);
@@ -13,10 +15,10 @@ TEST(JsonSpecOutput, UrlData)
                     "data": {"url": "https://vega.github.io/vega-datasets/data/cars.json"},
                     "mark": null
                 })"_json;
-    ASSERT_EQ(expected, result["application/vnd.vegalite.v3+json"]);
+    REQUIRE_EQ(expected, result["application/vnd.vegalite.v3+json"]);
 }
 
-TEST(JsonSpecOutput, Dataframe)
+TEST_CASE("Dataframe")
 {
     df_type val;
     val["country"] = {"India", "France", "France", "Germany", "India", "Germany", "France"};
@@ -40,10 +42,10 @@ TEST(JsonSpecOutput, Dataframe)
                     },
                     "mark": null
                 })"_json;
-    ASSERT_EQ(expected, result["application/vnd.vegalite.v3+json"]);
+    REQUIRE_EQ(expected, result["application/vnd.vegalite.v3+json"]);
 }
 
-TEST(JsonSpecOutput, SequenceGenerator)
+TEST_CASE("SequenceGenerator")
 {
     auto seq = sequence_params().start(0).stop(12.7).step(0.1).as("x");
     auto df = sequence_generator().sequence(seq);
@@ -55,6 +57,7 @@ TEST(JsonSpecOutput, SequenceGenerator)
                     "data": {"sequence": {"as": "x", "start": 0, "step": 0.1, "stop": 12.7}},
                     "mark": null
                 })"_json;
-    ASSERT_EQ(expected, result["application/vnd.vegalite.v3+json"]);
+    REQUIRE_EQ(expected, result["application/vnd.vegalite.v3+json"]);
+}
 }
 
diff --git a/test/test_encodings.cpp b/test/test_encodings.cpp
index a037a1f..a4e0284 100644
--- a/test/test_encodings.cpp
+++ b/test/test_encodings.cpp
@@ -1,9 +1,10 @@
-#include <gtest/gtest.h>
+#include "doctest/doctest.h"
 #include <xvega/xvega.hpp>
 
 using namespace xv;
-
-TEST(JsonSpecOutput, SingleEncodingChannel)
+TEST_SUITE("JsonSpecOutput")
+{
+TEST_CASE("SingleEncodingChannel")
 {
     auto df = url_data().url("https://vega.github.io/vega-datasets/data/cars.json");
     auto mp = mark_point();
@@ -19,10 +20,10 @@ TEST(JsonSpecOutput, SingleEncodingChannel)
                     "mark": {"type": "point"},
                     "width": 400
                 })"_json;
-    ASSERT_EQ(expected, result["application/vnd.vegalite.v3+json"]);
+    REQUIRE_EQ(expected, result["application/vnd.vegalite.v3+json"]);
 }
 
-TEST(JsonSpecOutput, DoubleEncodingChannel)
+TEST_CASE("DoubleEncodingChannel")
 {
     auto df = url_data().url("https://vega.github.io/vega-datasets/data/cars.json");
     auto mp = mark_point();
@@ -44,10 +45,10 @@ TEST(JsonSpecOutput, DoubleEncodingChannel)
                     "title": "Simple 2D Chart",
                     "width": 400
                 })"_json;
-    ASSERT_EQ(expected, result["application/vnd.vegalite.v3+json"]);
+    REQUIRE_EQ(expected, result["application/vnd.vegalite.v3+json"]);
 }
 
-TEST(JsonSpecOutput, TripleEncodingChannel)
+TEST_CASE("TripleEncodingChannel")
 {
     auto df = url_data().url("https://vega.github.io/vega-datasets/data/cars.json");
     auto mp = mark_point();
@@ -70,10 +71,10 @@ TEST(JsonSpecOutput, TripleEncodingChannel)
                     "mark": {"type": "point"},
                     "width": 400
                 })"_json;
-    ASSERT_EQ(expected, result["application/vnd.vegalite.v3+json"]);
+    REQUIRE_EQ(expected, result["application/vnd.vegalite.v3+json"]);
 }
 
-TEST(JsonSpecOutput, DoubleEncodingWithChannelOptions)
+TEST_CASE("DoubleEncodingWithChannelOptions")
 {
     auto df = url_data().url("https://vega.github.io/vega-datasets/data/cars.json");
     auto mb = mark_bar();
@@ -99,5 +100,6 @@ TEST(JsonSpecOutput, DoubleEncodingWithChannelOptions)
                     "mark": {"type": "bar"},
                     "width": 400
                 })"_json;
-    ASSERT_EQ(expected, result["application/vnd.vegalite.v3+json"]);
+    REQUIRE_EQ(expected, result["application/vnd.vegalite.v3+json"]);
+}
 }
diff --git a/test/test_marks.cpp b/test/test_marks.cpp
index 0c16018..ff7ae9f 100644
--- a/test/test_marks.cpp
+++ b/test/test_marks.cpp
@@ -1,9 +1,11 @@
-#include <gtest/gtest.h>
+#include "doctest/doctest.h"
 #include <xvega/xvega.hpp>
 
 using namespace xv;
 
-TEST(JsonSpecOutput, MarkPoint)
+TEST_SUITE("JsonSpecOutput")
+{
+TEST_CASE("MarkPoint")
 {
     auto df = url_data().url("https://vega.github.io/vega-datasets/data/cars.json");
     auto mp = mark_point();
@@ -14,10 +16,10 @@ TEST(JsonSpecOutput, MarkPoint)
                     "data": {"url": "https://vega.github.io/vega-datasets/data/cars.json"},
                     "mark": {"type": "point"}
                 })"_json;
-    ASSERT_EQ(expected, result["application/vnd.vegalite.v3+json"]);
+    REQUIRE_EQ(expected, result["application/vnd.vegalite.v3+json"]);
 }
 
-TEST(JsonSpecOutput, MarkPointWithCommonProperty)
+TEST_CASE("MarkPointWithCommonProperty")
 {
     auto df = url_data().url("https://vega.github.io/vega-datasets/data/cars.json");
     auto mp = mark_point().color("red");
@@ -28,10 +30,10 @@ TEST(JsonSpecOutput, MarkPointWithCommonProperty)
                     "data": {"url": "https://vega.github.io/vega-datasets/data/cars.json"},
                     "mark": {"color": "red", "type": "point"}
                 })"_json;
-    ASSERT_EQ(expected, result["application/vnd.vegalite.v3+json"]);
+    REQUIRE_EQ(expected, result["application/vnd.vegalite.v3+json"]);
 }
 
-TEST(JsonSpecOutput, MarkPointWithSpecificProperty)
+TEST_CASE("MarkPointWithSpecificProperty")
 {
     auto df = url_data().url("https://vega.github.io/vega-datasets/data/cars.json");
     auto mp = mark_point().color("red").size(100);
@@ -42,5 +44,6 @@ TEST(JsonSpecOutput, MarkPointWithSpecificProperty)
                     "data": {"url": "https://vega.github.io/vega-datasets/data/cars.json"},
                     "mark": {"color": "red", "size": 100, "type": "point"}
                 })"_json;
-    ASSERT_EQ(expected, result["application/vnd.vegalite.v3+json"]);
+    REQUIRE_EQ(expected, result["application/vnd.vegalite.v3+json"]);
+}
 }
diff --git a/test/test_selections.cpp b/test/test_selections.cpp
index f7a5eaa..79419f2 100644
--- a/test/test_selections.cpp
+++ b/test/test_selections.cpp
@@ -1,9 +1,11 @@
-#include <gtest/gtest.h>
+#include "doctest/doctest.h"
 #include <xvega/xvega.hpp>
 
 using namespace xv;
 
-TEST(JsonSpecOutput, IntervalSelection)
+TEST_SUITE("JsonSpecOutput")
+{
+TEST_CASE("IntervalSelection")
 {
     auto df = url_data().url("https://vega.github.io/vega-datasets/data/cars.json");
     auto mp = mark_point();
@@ -32,5 +34,6 @@ TEST(JsonSpecOutput, IntervalSelection)
                     "selection": {"selector_interval_nws23": {"type": "interval"}},
                     "width": 400
                 })"_json;
-    ASSERT_EQ(expected, result["application/vnd.vegalite.v3+json"]);
+    REQUIRE_EQ(expected, result["application/vnd.vegalite.v3+json"]);
+}
 }
diff --git a/test/test_transformations.cpp b/test/test_transformations.cpp
index c6a6014..bfdb689 100644
--- a/test/test_transformations.cpp
+++ b/test/test_transformations.cpp
@@ -1,9 +1,11 @@
-#include <gtest/gtest.h>
+#include "doctest/doctest.h"
 #include <xvega/xvega.hpp>
 
 using namespace xv;
 
-TEST(JsonSpecOutput, SingleTransformationWithLayering)
+TEST_SUITE("JsonSpecOutput")
+{
+TEST_CASE("SingleTransformationWithLayering")
 {
     auto df = url_data().url("https://vega.github.io/vega-datasets/data/cars.json");
     auto mp = mark_point();
@@ -43,10 +45,10 @@ TEST(JsonSpecOutput, SingleTransformationWithLayering)
                         }
                     ]
                 })"_json;
-    ASSERT_EQ(expected, result["application/vnd.vegalite.v3+json"]);
+    REQUIRE_EQ(expected, result["application/vnd.vegalite.v3+json"]);
 }
 
-TEST(JsonSpecOutput, MultipleTransformations)
+TEST_CASE("MultipleTransformations")
 {
     auto df = url_data().url("https://vega.github.io/vega-datasets/data/cars.json");
 
@@ -136,7 +138,6 @@ TEST(JsonSpecOutput, MultipleTransformations)
                         }
                     ]
                 })"_json;
-    ASSERT_EQ(expected, result["application/vnd.vegalite.v3+json"]);
+    REQUIRE_EQ(expected, result["application/vnd.vegalite.v3+json"]);
+}
 }
-
-
diff --git a/test/test_view_compositions.cpp b/test/test_view_compositions.cpp
index cd2eec3..9597036 100644
--- a/test/test_view_compositions.cpp
+++ b/test/test_view_compositions.cpp
@@ -1,9 +1,11 @@
-#include <gtest/gtest.h>
+#include "doctest/doctest.h"
 #include <xvega/xvega.hpp>
 
 using namespace xv;
 
-TEST(JsonSpecOutput, HorizontalConcatenation)
+TEST_SUITE("JsonSpecOutput")
+{
+TEST_CASE("HorizontalConcatenation")
 {
     auto df = url_data().url("https://vega.github.io/vega-datasets/data/cars.json");
     auto mp = mark_point();
@@ -48,10 +50,10 @@ TEST(JsonSpecOutput, HorizontalConcatenation)
                         }
                     ]
                 })"_json;
-    ASSERT_EQ(expected, result["application/vnd.vegalite.v3+json"]);
+    REQUIRE_EQ(expected, result["application/vnd.vegalite.v3+json"]);
 }
 
-TEST(JsonSpecOutput, VerticalConcatenation)
+TEST_CASE("VerticalConcatenation")
 {
     auto df = url_data().url("https://vega.github.io/vega-datasets/data/cars.json");
     auto mp = mark_point();
@@ -96,10 +98,10 @@ TEST(JsonSpecOutput, VerticalConcatenation)
                         }
                     ]
                 })"_json;
-    ASSERT_EQ(expected, result["application/vnd.vegalite.v3+json"]);
+    REQUIRE_EQ(expected, result["application/vnd.vegalite.v3+json"]);
 }
 
-TEST(JsonSpecOutput, Layering)
+TEST_CASE("Layering")
 {
     auto df = url_data().url("https://vega.github.io/vega-datasets/data/cars.json");
     auto mp = mark_point();
@@ -138,5 +140,6 @@ TEST(JsonSpecOutput, Layering)
                         }
                     ]
                 })"_json;
-    ASSERT_EQ(expected, result["application/vnd.vegalite.v3+json"]);
+    REQUIRE_EQ(expected, result["application/vnd.vegalite.v3+json"]);
+}
 }