28

We need to write unit tests for a wxWidgets application using Google Test Framework. The problem is that wxWidgets uses the macro IMPLEMENT_APP(MyApp) to initialize and enter the application main loop. This macro creates several functions including int main(). The google test framework also uses macro definitions for each test.

One of the problems is that it is not possible to call the wxWidgets macro from within the test macro, because the first one creates functions.. So, we found that we could replace the macro with the following code:

wxApp* pApp = new MyApp(); 
wxApp::SetInstance(pApp);
wxEntry(argc, argv);

That's a good replacement, but wxEntry() call enters the original application loop. If we don't call wxEntry() there are still some parts of the application not initialized.

The question is how to initialize everything required for a wxApp to run, without actually running it, so we are able to unit test portions of it?

m_pGladiator
  • 8,462
  • 7
  • 43
  • 61

6 Answers6

17

Just been through this myself with 2.8.10. The magic is this:

// MyWxApp derives from wxApp
wxApp::SetInstance( new MyWxApp() );
wxEntryStart( argc, argv );
wxTheApp->CallOnInit();

// you can create top level-windows here or in OnInit()
...
// do your testing here

wxTheApp->OnRun();
wxTheApp->OnExit();
wxEntryCleanup();

You can just create a wxApp instance rather than deriving your own class using the technique above.

I'm not sure how you expect to do unit testing of your application without entering the mainloop as many wxWidgets components require the delivery of events to function. The usual approach would be to run unit tests after entering the main loop.

Andreas
  • 9,245
  • 9
  • 49
  • 97
Daniel Paull
  • 6,797
  • 3
  • 32
  • 41
9
IMPLEMENT_APP_NO_MAIN(MyApp);
IMPLEMENT_WX_THEME_SUPPORT;

int main(int argc, char *argv[])
{
    wxEntryStart( argc, argv );
    wxTheApp->CallOnInit();
    wxTheApp->OnRun();

    return 0;
}
Byllgrim
  • 103
  • 1
  • 2
5

You want to use the function:

bool wxEntryStart(int& argc, wxChar **argv)

instead of wxEntry. It doesn't call your app's OnInit() or run the main loop.

You can call wxTheApp->CallOnInit() to invoke OnInit() when needed in your tests.

You'll need to use

void wxEntryCleanup()

when you're done.

kbluck
  • 11,530
  • 4
  • 25
  • 25
  • Actually, this does not work. The application is still not initialized – m_pGladiator Oct 20 '08 at 10:24
  • Can you be more specific about what "not initialized" means? If you read the source code, you will see that wxEntry really doesn't do much more than invoke wxEntryStart() and then call the "OnInit()" and "OnRun()". I would think you'd want to call OnInit() manually in your tests. – kbluck Oct 20 '08 at 15:59
  • OK, that's something I missed - OnInit(). I'll try it. – m_pGladiator Oct 21 '08 at 13:00
  • By Initialized, means I could create a main window, without showing it and keeping control in the UT, which enables the UT to create classes, which requires this main window in order to work. – m_pGladiator Oct 21 '08 at 13:02
  • Main windows are conventionally created in OnInit(). You can call wxTheApp->CallOnInit() to invoke it manually. However, if the message pump is not running, most event-related parts of the window will not work. Perhaps this topic is moving into a new, different question about how to unit test GUIs? – kbluck Oct 21 '08 at 19:30
1

It looks like doing the tests in the wxApp::OnRun() function can perhaps work. Here's code that tests a dialog's title with cppUnitLite2.

 
#include "wx/wxprec.h"

#ifdef __BORLANDC__
#pragma hdrstop
#endif

#ifndef WX_PRECOMP
    #include "wx/wx.h"
#endif
#include  "wx/app.h"  // use square braces for wx includes: I made quotes to overcome issue in HTML render
#include  "wx/Frame.h"
#include "../CppUnitLite2\src/CppUnitLite2.h"
#include "../CppUnitLite2\src/TestResultStdErr.h" 
#include "../theAppToBeTested/MyDialog.h"
 TEST (MyFirstTest)
{
    // The "Hello World" of the test system
    int a = 102;
    CHECK_EQUAL (102, a);
}

 TEST (MySecondTest)
 {
    MyDialog dlg(NULL);   // instantiate a class derived from wxDialog
    CHECK_EQUAL ("HELLO", dlg.GetTitle()); // Expecting this to fail: title should be "MY DIALOG" 
 }

class MyApp: public wxApp
{
public:
    virtual bool OnInit();
    virtual int OnRun();
};

IMPLEMENT_APP(MyApp)

bool MyApp::OnInit()
{   
    return true;
}

int MyApp::OnRun()
{
    fprintf(stderr, "====================== Running App Unit Tests =============================\n");
    if ( !wxApp::OnInit() )
        return false;

    TestResultStdErr result;
    TestRegistry::Instance().Run(result);   
    fprintf(stderr, "====================== Testing end: %ld errors =============================\n", result.FailureCount() );

    return result.FailureCount(); 
}
-1

You could possibly turn the situation around:

Initialize and start the wxPython app including the main loop, then run the unit tests from within the app. I think there is a function called upon main loop entry, after all init stuff is done.

Ber
  • 40,356
  • 16
  • 72
  • 88
  • The goal is to test the application with UT, not UT with app – m_pGladiator Oct 20 '08 at 10:25
  • Unless you find a way to do the complete init (and so far nobody suggested a viable solution) The goal that was stated in a comment above can well be achieved this way. I have done similar things already in wxPython. – Ber Oct 23 '08 at 07:57
  • +1 from me, this is how I did my tests in wxWidgets after trying to solve the problem myself. – SteveL May 28 '09 at 15:53
-1

Have you tried the IMPLEMENT_APP_NO_MAIN macro? The comment provided above the macro definition suggests it might do what you need it to.

From <wxWidgets' source dir>\include\wx.h:

// Use this macro if you want to define your own main() or WinMain() function
// and call wxEntry() from there.
#define IMPLEMENT_APP_NO_MAIN(appname)                                      \
   wxAppConsole *wxCreateApp()                                             \
    {                                                                       \
        wxAppConsole::CheckBuildOptions(WX_BUILD_OPTIONS_SIGNATURE,         \
                                        "your program");                    \
        return new appname;                                                 \
    }                                                                       \
    wxAppInitializer                                                        \
        wxTheAppInitializer((wxAppInitializerFunction) wxCreateApp);        \
    DECLARE_APP(appname)                                                    \
    appname& wxGetApp() { return *wx_static_cast(appname*, wxApp::GetInstance()); }
antik
  • 5,282
  • 1
  • 34
  • 47
  • 1
    This macro is actually reduced to the first two lines of code in the question. The problem is that before calling wxEntry() there are some parts of wxApp still not initialized. wxEntry() enters the app loop and the control is passed to wxWidgets. I'd like to init the wxApp, without running it. – m_pGladiator Oct 17 '08 at 07:58