At the moment, I have created a test project using a project template from Visual Studio Unit Test App (Winui 3) and my code looks like this
namespace TestMyWinrtApp
{
TEST_CLASS(CppUnitTests)
{
public:
TEST_METHOD(CppTestOne)
{
try {
auto bp = winrt::make< winrt::MyUI::implementation::BlankPage>();
Button btn = bp.FindName(L"myButton").as<winrt::Microsoft::UI::Xaml::Controls::Button>();
winrt::hstring btnTag = btn.Tag().as<winrt::hstring>();
Assert::IsTrue(btnTag == L"MyTag");
} catch (const winrt::hresult_error& ex) {
MessageBox(
NULL,
ex.message().c_str(),
L"Error",
MB_OK | MB_ICONINFORMATION
);
}
}
};
}
However, after starting the tests, I get the exception: "The Application Called An Interface that was Marshalled for a Different Thread". As far as I know, this exception thrown is due to the fact that I try to create an winrt::make< winrt::MyUI::implementation::BlankPage>()
UI element outside the UI thread. I found a solution for C#
which is to add an [UITestMethod]
attribute for the test method. But how i can do unit tests like this in C++ test project?
The Problem is solved. As @Simon Mourier & @György Kőszeg suggested in comments, we can We can add a task to the ui thread queue to use the ui elements there. To access the ui thread queue, I used a global variable because I did not find any other way to access the ui thread queue other than through a global variable whose value is assigned in the OnLaunched method in the App class after activating the main window. Finally, solution looks like this:
#include <winrt/base.h>
#include <winrt/Microsoft.UI.Xaml.Controls.h>
using namespace winrt::Microsoft::UI::Xaml::Controls;
extern winrt::Microsoft::UI::Dispatching::DispatcherQueue UIDispatcherQueue;
TEST(UITEST, first)
{
std::promise<bool> p;
std::future<bool> f = p.get_future();
bool enqueued = UIDispatcherQueue.TryEnqueue(winrt::Microsoft::UI::Dispatching::DispatcherQueuePriority::High, [&p]()
{
auto bp = winrt::make< winrt::WinUIGtest::implementation::BlankPage>();
Button btn = bp.FindName(L"myButton").as<Button>();
winrt::hstring btnTag = btn.Tag().as<winrt::hstring>();
bool isEquals = (btnTag == L"MyTag");
p.set_value(isEquals);
});
bool res = f.get();
ASSERT_EQ(res, true);
}
As you can see, I was also changed test framework to google test. If you want to do so, then you need:
Note: If you want to run tests which should enqueue callback in the UI thread, you should run tests in the separate thread, otherwise this action seems like produced deadlock.
// allocate console to see gtest output
if (AllocConsole()) {
FILE* pStdout, * pStderr;
freopen_s(&pStdout, "CONOUT$", "w", stdout);
freopen_s(&pStderr, "CONOUT$", "w", stderr);
}
// run gtest in separate thread
std::thread([]
{
int argc = 1;
char** fakeArgv = new char* [1];
char* path = new char[1024];
strcpy_s(path, 1024, R"(C:\Users\UserName\source\repos\WinUIGtest\x64\Debug\WinUIGtest\WinUIGtest.exe)");
fakeArgv[0] = path;
::testing::InitGoogleTest(&argc, fakeArgv);
//::testing::InitGoogleMock(&argc, fakeArgv);
RUN_ALL_TESTS();
}).detach();
// disable standard tests
//winrt::Microsoft::VisualStudio::TestPlatform::TestExecutor::WinRTCore::UnitTestClient::Run(m_args);