Search code examples
c++testingbazelcatch-unit-testcatch2

How to set up Catch2 in Bazel project


I have started a simple C++ project that uses Bazel as build system and would like to add Catch2 to it, as test framework.

This is what my project looks like so far:

WORKSPACE -> empty file
src/
  Money.hpp
  Money.cpp
BUILD

where BUILD is just

cc_library(
  name = "Money",
  srcs = ["Money.cpp"],
  hdrs = ["Money.hpp"]
)

I would like to be able to create tests for each cc_library, in this case for Money. I tried setting it up but got confused with Catch2 main. Any advice on how to do this best is appreciated!


Solution

  • After some back and forth I managed to get this working, for Bazel 0.16.1 and Catch2 2.4.0.

    First let's create directory test/ next to src/, to keep our tests there.

    In order to use Catch2, we need to download catch.hpp. Catch2 is header only library, meaning that one file is all we need. I put it in test/vendor/catch2/.
    Then, we need to define to bazel how to use it. In test/vendor/catch2 we create following BUILD file:

    cc_library(
      name = "catch2",
      hdrs = ["catch.hpp"],
      visibility = ["//test:__pkg__"]
    )
    

    Now Bazel recognizes Catch2 as a library. We added visibility attribute, so that it can be used from the //test package (which is defined by BUILD in /test directory).

    Next, Catch2 requires us to define one translation unit with correctly defined main method. Following their instructions, we create test/main.cpp file:

    #define CATCH_CONFIG_MAIN
    #include "catch.hpp"
    

    Now, we write our test, in test/Money.test.cpp:

    #include "catch.hpp"
    #include "Money.hpp"
    
    TEST_CASE("Money works.") {
      ...
    }
    

    Finally, we need to explain to Bazel how to build all this. Notice that we directly included Money.hpp and catch.hpp in our files, with no relative path, so that is also smth we need to keep in mind. We create following test/BUILD file:

    # We describe to Bazel how to build main.cpp.
    # It includes "catch.hpp" directly, so we need to add
    # "-Itest/vendor/catch2" compiler option.
    cc_library(
        name = "catch-main",
        srcs = ["main.cpp"],
        copts = ["-Itest/vendor/catch2"],
        deps = [
            "//test/vendor/catch2"
        ]
    )
    
    # Here we define our test. It needs to build together with the catch2
    # main that we defined above, so we add it to deps. We directly
    # include src/Money.hpp and test/vendor/catch2/catch.hpp in
    # Money.test.cpp, so we need to add their parent directories as copts.
    # We also add Money and catch2 as dependencies.
    cc_test(
        name = "Money",
        srcs = ["Money.test.cpp"],
        copts = ["-Itest/vendor/catch2/", "-Isrc/"],
        deps = [
            # Or "//test/vendor/catch2:catch2", it is the same.
            "//test/vendor/catch2",
            "catch-main",
            "//src:Money"
        ]
    )
    
    # Test suite that runs all the tests.
    test_suite(
        name = "all-tests",
        tests = [
            "Money"
        ]
    )
    

    Finally, we just need to add visibility attribute to src/BUILD so that it can be accessed from tests. We modify src/BUILD to look like this:

    cc_library(
        name = "Money",
        srcs = ["Money.cpp"],
        hdrs = ["Money.hpp"],
        visibility = ["//test:__pkg__"]
    )
    

    Final file structure looks like this:

    WORKSPACE
    src/
      Money.hpp
      Money.cpp
      BUILD
    test/
      BUILD
      main.cpp
      Money.test.cpp
      vendor/
        catch2/
          catch.hpp
          BUILD
    

    Now you can run your tests with bazel test //test:all-tests!

    I created Github repo with this example, you can check it out here. I also turned it into a blog post.