Currently, it is not possible to test the values of C++20 ranges with matchers the same way that containers from the standard library can be tested. As more C++ applications use ranges throughout their public API it would be extremely helpful to have ranges supported by GoogleTest.
Previous related issues
Issues #3403 and #3564 touch on this, but both don't go as in-depth and only propose changing existing matchers instead of considering new ones.
Alternate Solutions
Wrapping ranges with a container
If the writer of a unit test wraps a C++20 ranges value in a standard library container, the existing matchers can be used the same way they are now.
While this works, it is far more verbose and requires more boilerplate to be written for every unit test that deals with a range. Additionally, the increased verbosity makes it less clear how exactly the range is being tested.
Calculating test values in separate statements
If the resulting value to test is stored in a variable ahead of time, a container matcher doesn't need to be used at all.
While this works, it requires every unit test involving ranges to manually calculate whether the test should pass or not, which defeats the purpose of container matchers. Additionally, a failing test written this way would produce a far less helpful error message than a container matchers would as only the boolean result is used as opposed to the range being tested.
Custom (user-written) matchers
A test unit writer could make use of the MATCHER macros to define their own matchers for C++20 ranges.
While this works, it requires the programmer to define a new corresponding matcher for every single container matcher themself. The user would then need to (or at least probably should) test each matcher to make sure they work correctly. These range-specific matchers may be frequently needed in a variety of C++20 applications and shouldn't require users to define them themselves for each project that uses them. Additionally, the C++20 ranges library is part of the standard library itself, and therefore is very likely to be used in user code.
Describe the proposal.
Summary
It would be nice if all of the currently existing container matchers either had corresponding matchers that supported C++20 ranges, or supported C++20 ranges themselves.
If changing the interface of the existing matchers would break existing code, new matchers that are specific to ranges could be defined instead. (In this context, all containers are ranges but not all ranges are containers.)
Separate overloads that take a start and end iterator could be defined as well, possibly for pre-C++20 code. This might not be necessary though.
To avoid introducing a breaking change, feature-test macros could be used internally for ranges-specific code.
Example
Here's an example of what this might look like:
// bands.h
#pragma once
#include <ranges>
#include <string>
struct Artist
{
std::string name;
bool is_lead_singer;
};
class Band
{
public:
// Accessor method that returns a range of every band member
std::ranges::forward_range auto band_members() const;
// Accessor method that returns a read-only reference to the band's lead singer
const Artist& lead_singer() const;
// For simplicity just assume the constructor creates several default band members
Band();
/*...more code...*/
};
// bands_tests.cpp
#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include <ranges>
#include "bands.h"
TEST(BandTests, BandHasAtLeastOneMember)
{
const Band band{};
std::ranges::forward_range auto band_members{ band.band_members() };
EXPECT_THAT(band_members, Not(IsEmpty());
// OR: EXPECT_THAT(band_members, Not(IsEmptyRange());
}
TEST(BandTests, LeadSingerOfBandIsActuallyLeadSinger)
{
const Band band{};
EXPECT_TRUE(band.lead_singer().is_lead_singer);
}
TEST(BandTests, LeadSingerOfBandIsBandMember)
{
const Band band{};
std::ranges::forward_range auto band_members{ band.band_members() };
const Artist& lead_singer{ band.lead_singer() };
EXPECT_THAT(band_members, Contains(lead_singer));
// OR: EXPECT_THAT(band_members, RangeContains(lead_singer));
}
TEST(BandTests, BandHasOnlyOneLeadSinger)
{
const Band band{};
auto is_lead_singer_fn = [](const Artist& artist){
return artist.is_lead_singer;
}
std::ranges::input_range auto lead_singers {
band.band_members() | std::views::filter(is_lead_singer_fn)
};
EXPECT_THAT(lead_singers, SizeIs(1));
// OR: EXPECT_THAT(lead_singers, RangeSizeIs(1));
}
Notes
I can, and would be happy to, attempt to implement some or all of the range matchers if that works best.
I have yet to sign the Contributor License Agreement (CLA) but can do so if needed.
Is the feature specific to an operating system, compiler, or build system version?
Changes should only be noticeable to C++ versions 20 and later.
Does the feature exist in the most recent commit?
No
Why do we need this feature?
Motivation
Currently, it is not possible to test the values of C++20 ranges with matchers the same way that containers from the standard library can be tested. As more C++ applications use ranges throughout their public API it would be extremely helpful to have ranges supported by GoogleTest.
Previous related issues
Issues #3403 and #3564 touch on this, but both don't go as in-depth and only propose changing existing matchers instead of considering new ones.
Alternate Solutions
Wrapping ranges with a container
If the writer of a unit test wraps a C++20 ranges value in a standard library container, the existing matchers can be used the same way they are now.
Example
Why not this?
While this works, it is far more verbose and requires more boilerplate to be written for every unit test that deals with a range. Additionally, the increased verbosity makes it less clear how exactly the range is being tested.
Calculating test values in separate statements
If the resulting value to test is stored in a variable ahead of time, a container matcher doesn't need to be used at all.
Example
Why not this?
While this works, it requires every unit test involving ranges to manually calculate whether the test should pass or not, which defeats the purpose of container matchers. Additionally, a failing test written this way would produce a far less helpful error message than a container matchers would as only the boolean result is used as opposed to the range being tested.
Custom (user-written) matchers
A test unit writer could make use of the MATCHER macros to define their own matchers for C++20 ranges.
Example
Why not this?
While this works, it requires the programmer to define a new corresponding matcher for every single container matcher themself. The user would then need to (or at least probably should) test each matcher to make sure they work correctly. These range-specific matchers may be frequently needed in a variety of C++20 applications and shouldn't require users to define them themselves for each project that uses them. Additionally, the C++20 ranges library is part of the standard library itself, and therefore is very likely to be used in user code.
Describe the proposal.
Summary
It would be nice if all of the currently existing container matchers either had corresponding matchers that supported C++20 ranges, or supported C++20 ranges themselves.
If changing the interface of the existing matchers would break existing code, new matchers that are specific to ranges could be defined instead. (In this context, all containers are ranges but not all ranges are containers.)
Separate overloads that take a start and end iterator could be defined as well, possibly for pre-C++20 code. This might not be necessary though.
To avoid introducing a breaking change, feature-test macros could be used internally for ranges-specific code.
Example
Here's an example of what this might look like:
Notes
Is the feature specific to an operating system, compiler, or build system version?
Changes should only be noticeable to C++ versions 20 and later.