From a7eafe47932d60c8420a5f60efd64fc4a1c97ddd Mon Sep 17 00:00:00 2001
From: Ming Rui Zhang <mingrui.zhang@savoirfairelinux.com>
Date: Fri, 16 Apr 2021 17:18:09 -0400
Subject: [PATCH] test: add building blocks for qml and GTest on windows

- Also updated the INSTALL.md

Gitlab: #385

Change-Id: Ib5bf9f0348dbc6da57e586475d698fe8dec0351b
---
 CMakeLists.txt                                |  46 ++--
 INSTALL.md                                    |  24 +-
 ...ploy.cmake => windows_daemon_deploy.cmake} |   8 +-
 cmake/windows_qt_deploy.cmake                 |  14 ++
 make-client.py                                | 124 ++++++++--
 tests/CMakeLists.txt                          | 224 ++++++++++++++----
 tests/googletest/CMakeLists.txt.in            |  15 ++
 tests/qml/main.cpp                            | 188 ++++++++++++++-
 tests/qml/src/tst_LocalAccount.qml            |  32 +++
 tests/qml/src/tst_PresenceIndicator.qml       |  45 ++++
 tests/qml/tst_test.qml                        |  10 -
 tests/unittests/accountadapter_unittest.cpp   |  89 +++++++
 tests/unittests/dummy_unittest.cpp            |   7 -
 tests/unittests/globaltestenvironment.h       |  19 ++
 tests/unittests/main_unittest.cpp             |  47 ++++
 tests/unittests/main_unittests.cpp            |  10 -
 16 files changed, 778 insertions(+), 124 deletions(-)
 rename cmake/{windows_deploy.cmake => windows_daemon_deploy.cmake} (78%)
 create mode 100644 cmake/windows_qt_deploy.cmake
 create mode 100644 tests/googletest/CMakeLists.txt.in
 create mode 100644 tests/qml/src/tst_LocalAccount.qml
 create mode 100644 tests/qml/src/tst_PresenceIndicator.qml
 delete mode 100644 tests/qml/tst_test.qml
 create mode 100644 tests/unittests/accountadapter_unittest.cpp
 delete mode 100644 tests/unittests/dummy_unittest.cpp
 create mode 100644 tests/unittests/globaltestenvironment.h
 create mode 100644 tests/unittests/main_unittest.cpp
 delete mode 100644 tests/unittests/main_unittests.cpp

diff --git a/CMakeLists.txt b/CMakeLists.txt
index d76715e7d..77dc4ea31 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -142,6 +142,15 @@ set(QML_LIBS_LIST
     QuickControls2
     WebEngine)
 
+set(WINDOWS_SYS_LIBS Shell32.lib
+                     Ole32.lib
+                     Advapi32.lib
+                     Shlwapi.lib
+                     User32.lib
+                     Gdi32.lib
+                     Crypt32.lib
+                     Strmiids.lib)
+
 if(MSVC)
     # preprocessor defines
     add_definitions(-DUNICODE -DQT_NO_DEBUG -DNDEBUG)
@@ -181,9 +190,9 @@ if(MSVC)
         if(BETA)
             message("Beta config enabled")
             add_definitions(-DBETA)
-            set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE ${PROJECT_SOURCE_DIR}/x64/Beta)
+            set(JAMI_OUTPUT_DIRECTORY_RELEASE ${PROJECT_SOURCE_DIR}/x64/Beta)
         else()
-            set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE ${PROJECT_SOURCE_DIR}/x64/Release)
+            set(JAMI_OUTPUT_DIRECTORY_RELEASE ${PROJECT_SOURCE_DIR}/x64/Release)
         endif()
     endif()
 
@@ -301,14 +310,13 @@ if(MSVC)
     target_link_libraries(${PROJECT_NAME}
                           ${QML_LIBS}
                           ${QRENCODE_LIB}
-                          Shell32.lib
-                          Ole32.lib
-                          Advapi32.lib
-                          Shlwapi.lib
-                          User32.lib
-                          Gdi32.lib
-                          Crypt32.lib
-                          Strmiids.lib)
+                          ${WINDOWS_SYS_LIBS})
+
+    # specify output executable files
+    set_target_properties(${PROJECT_NAME}
+        PROPERTIES
+        RUNTIME_OUTPUT_DIRECTORY_RELEASE "${JAMI_OUTPUT_DIRECTORY_RELEASE}"
+    )
 
     if(NOT DEFINED ReleaseCompile)
         # executable icon
@@ -353,10 +361,15 @@ if(MSVC)
                                    -DCOPY_TO_PATH=$<TARGET_FILE_DIR:${PROJECT_NAME}>
                                    -DDRING_PATH=${DRING}
                                    -DPROJECT_ROOT_DIR=${PROJECT_SOURCE_DIR}
+                                   -P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/windows_daemon_deploy.cmake)
+
+        add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
+                           WORKING_DIRECTORY "$<TARGET_FILE_DIR:${PROJECT_NAME}>"
+                           COMMAND ${CMAKE_COMMAND} -DTIME_STAMP_FILE=${TIME_STAMP_FILE}
                                    -DWIN_DEPLOY_QT_PATH=${CMAKE_PREFIX_PATH}/bin
                                    -DQML_SRC_DIR=${SRC_DIR}
                                    -DEXE_NAME=$<TARGET_FILE:${PROJECT_NAME}>
-                                   -P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/windows_deploy.cmake)
+                                   -P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/windows_qt_deploy.cmake)
 
         # create time stamp
         add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
@@ -377,11 +390,6 @@ else()
                           ${LIBNOTIFY_LIBRARIES}
                           ${LIBGDKPIXBUF_LIBRARIES})
 
-    if(ENABLE_TESTS)
-        message("Add Jami tests")
-        add_subdirectory(tests)
-    endif()
-
     # Installation rules
     install(TARGETS jami-qt
         RUNTIME DESTINATION bin)
@@ -481,3 +489,9 @@ else()
     add_custom_target(uninstall
         COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake)
 endif()
+
+# test
+if(ENABLE_TESTS)
+    message("Add Jami tests")
+    add_subdirectory(tests)
+endif()
diff --git a/INSTALL.md b/INSTALL.md
index a59c2f96b..c00ce13fa 100644
--- a/INSTALL.md
+++ b/INSTALL.md
@@ -111,14 +111,14 @@ Only 64-bit MSVC build can be compiled.
 
   | | Prebuild | Module |
   |---|---|---|
-  | Components: | msvc2017_64 | Qt WebEngine |
+  | Components: | msvc2019_64 | Qt WebEngine |
 
-- Download [Visual Studio](https://visualstudio.microsoft.com/) (version >= 2015) <br>
+- Download [Visual Studio](https://visualstudio.microsoft.com/) (version >= 2019) <br>
 - Install Qt Vs Tools under extensions, and configure msvc2017_64 path under Qt Options <br>
 
   | | Qt Version | SDK | Toolset |
   |---|---|---|---|
-  | Minimum requirement: | 5.9.4 | 10.0.16299.0 | V141 |
+  | Minimum requirement: | 5.14.0 | 10.0.16299.0 | V142 |
 
 - Install [Python3](https://www.python.org/downloads/) for Windows
 
@@ -156,13 +156,13 @@ Only 64-bit MSVC build can be compiled.
 > Note: <br>
 > To control the toolset and the sdk version that are used by msbuild, you can use ```--toolset``` and ```--sdk``` options <br>
 > To control which Qt version should be used (qmake, windeployqt), uou can use ```--qtver``` option <br>
-> By default: ```toolset=v141```, ```sdk=10.0.16299.0```,  ```qtver=5.9.4``` <br>
+> By default: ```toolset=v142```, ```sdk=10.0.16299.0```,  ```qtver=5.15.0``` <br>
 > For example:
 ```sh
-    python make-ring.py --install --toolset v142 --sdk 10.0.18362.0 --qtver 5.14.0
+    python make-ring.py --install --toolset v142 --sdk 10.0.16299.0 --qtver 5.15.0
 ```
 
-### Build Module individually
+### Build Module Individually
 
 - Jami-qt also support building each module (daemon, lrc, jami-qt) seperately
 
@@ -199,7 +199,6 @@ Only 64-bit MSVC build can be compiled.
     cd client-windows
     python make-client.py -d
     python make-client.py -b
-    powershell -ExecutionPolicy Unrestricted -File copy-runtime-files.ps1
 ```
 
 **Note**
@@ -213,7 +212,16 @@ Only 64-bit MSVC build can be compiled.
 - In Visual Studio, download WiX Toolset Visual Studio Extension.
 - Build client-windows project first, then the JamiInstaller project, msi package should be stored in ring-project\client-windows\JamiInstaller\bin\Release
 
-#### Debugging
+## Testing For Client-qt
+
+- We currently use [GoogleTest](https://github.com/google/googletest) and [Qt Quick Test](https://doc.qt.io/qt-5/qtquicktest-index.html#introduction) in our product. To build and run tests, you could use the following command.
+
+```
+    python make-client.py -b -wt
+    python make-client.py runtests
+```
+
+## Debugging
 
 Compile the client with `BUILD=Debug` and compile LibRingClient with
 `-DCMAKE_BUILD_TYPE=Debug`
\ No newline at end of file
diff --git a/cmake/windows_deploy.cmake b/cmake/windows_daemon_deploy.cmake
similarity index 78%
rename from cmake/windows_deploy.cmake
rename to cmake/windows_daemon_deploy.cmake
index f4d8c74b6..82d8a94bf 100644
--- a/cmake/windows_deploy.cmake
+++ b/cmake/windows_daemon_deploy.cmake
@@ -1,7 +1,7 @@
 if (EXISTS ${TIME_STAMP_FILE})
-    message("No need to deploy")
+    message("No need for daemon deployment")
 else()
-    message("Deploying ...")
+    message("Daemon deploying ...")
     file(COPY "${DRING_PATH}/contrib/build/ffmpeg/Build/win32/x64/bin/avcodec-58.dll"
               "${DRING_PATH}/contrib/build/ffmpeg/Build/win32/x64/bin/avutil-56.dll"
               "${DRING_PATH}/contrib/build/ffmpeg/Build/win32/x64/bin/avformat-58.dll"
@@ -15,8 +15,4 @@ else()
               "${PROJECT_ROOT_DIR}/images/jami.ico"
               "${PROJECT_ROOT_DIR}/License.rtf"
          DESTINATION ${COPY_TO_PATH})
-    execute_process(COMMAND "${WIN_DEPLOY_QT_PATH}/windeployqt.exe"
-                            --verbose 1
-                            --qmldir ${QML_SRC_DIR}
-                            --release ${EXE_NAME})
 endif()
\ No newline at end of file
diff --git a/cmake/windows_qt_deploy.cmake b/cmake/windows_qt_deploy.cmake
new file mode 100644
index 000000000..8957d1bdb
--- /dev/null
+++ b/cmake/windows_qt_deploy.cmake
@@ -0,0 +1,14 @@
+if (EXISTS ${TIME_STAMP_FILE})
+    message("No need for Qt deployment in dir " ${QML_SRC_DIR})
+else()
+    message("Qt deploying in dir " ${QML_SRC_DIR})
+    execute_process(COMMAND "${WIN_DEPLOY_QT_PATH}/windeployqt.exe"
+                            --verbose 1
+                            --qmldir ${QML_SRC_DIR}
+                            --release ${EXE_NAME})
+    if (DEFINED OFF_SCREEN_PLUGIN_REQUESTED)
+        # for not showing window when testing
+        file(COPY "${OFF_SCREEN_PLUGIN_PATH}/qoffscreen.dll"
+             DESTINATION ${COPY_TO_PATH})
+    endif()
+endif()
\ No newline at end of file
diff --git a/make-client.py b/make-client.py
index cebebfcc4..7a7a1cec8 100644
--- a/make-client.py
+++ b/make-client.py
@@ -22,6 +22,15 @@ host_is_64bit = (False, True)[platform.machine().endswith('64')]
 this_dir = os.path.dirname(os.path.realpath(__file__))
 build_dir = this_dir + '\\build'
 
+# project path
+jami_qt_project = build_dir + '\\jami-qt.vcxproj'
+unit_test_project = build_dir + '\\tests\\unittests.vcxproj'
+qml_test_project = build_dir + '\\tests\\qml_tests.vcxproj'
+
+# test executable command
+qml_test_exe = this_dir + '\\x64\\test\\qml_tests.exe -input ' + this_dir + '\\tests\\qml'
+unit_test_exe = this_dir + '\\x64\\test\\unittests.exe'
+
 class QtVerison(Enum):
     Major = 0
     Minor = 1
@@ -83,17 +92,18 @@ def findMSBuild():
         if filename in files:
             return os.path.join(root, filename)
 
-def getMSBuildArgs(arch, config_str, configuration_type, toolset):
+def getMSBuildArgs(arch, config_str, toolset, configuration_type=''):
     msbuild_args = [
         '/nologo',
         '/verbosity:minimal',
         '/maxcpucount:' + str(multiprocessing.cpu_count()),
         '/p:Platform=' + arch,
         '/p:Configuration=' + config_str,
-        '/p:ConfigurationType=' + configuration_type,
         '/p:useenv=true']
     if (toolset != ''):
         msbuild_args.append('/p:PlatformToolset=' + toolset)
+    if (configuration_type != ''):
+        msbuild_args.append('/p:ConfigurationType=' + configuration_type)
     return msbuild_args
 
 def getVSEnv(arch='x64', platform='', version=''):
@@ -119,6 +129,20 @@ def getVSEnvCmd(arch='x64', platform='', version=''):
     return vcEnvInit
 
 
+def replace_necessary_vs_prop(project_path, toolset, sdk_version):
+    # force toolset
+    replace_vs_prop(project_path,
+                    'PlatformToolset',
+                    toolset)
+    # force unicode
+    replace_vs_prop(project_path,
+                    'CharacterSet',
+                    'Unicode')
+    # force sdk_version
+    replace_vs_prop(project_path,
+                    'WindowsTargetPlatformVersion',
+                    sdk_version)
+
 def build_project(msbuild, msbuild_args, proj, env_vars):
     args = []
     args.extend(msbuild_args)
@@ -162,7 +186,7 @@ def deps(arch, toolset, qtver):
     msbuild = findMSBuild()
     if not os.path.isfile(msbuild):
         raise IOError('msbuild.exe not found. path=' + msbuild)
-    msbuild_args = getMSBuildArgs(arch, 'Release-Lib', 'StaticLibrary', toolset)
+    msbuild_args = getMSBuildArgs(arch, 'Release-Lib', toolset)
 
     this_dir = os.path.dirname(os.path.realpath(__file__))
     proj_path = this_dir + '\\qrencode-win32\\qrencode-win32\\vc8\\qrcodelib\\qrcodelib.vcxproj'
@@ -170,7 +194,8 @@ def deps(arch, toolset, qtver):
     build_project(msbuild, msbuild_args, proj_path, vs_env_vars)
 
 
-def build(arch, toolset, sdk_version, config_str, project_path_under_current_path, qtver, force_option=True):
+def build(arch, toolset, sdk_version, config_str, project_path_under_current_path, qtver,
+          enable_test, force_option=True):
     print("Building with Qt " + qtver)
 
     configuration_type = 'StaticLibrary'
@@ -194,6 +219,9 @@ def build(arch, toolset, sdk_version, config_str, project_path_under_current_pat
         '-DQt5LinguistTools_DIR=' + qt_cmake_dir + 'Qt5LinguistTools',
         '-DQt5Concurrent_DIR=' + qt_cmake_dir + 'Qt5Concurrent',
         '-DQt5Gui_DIR=' + qt_cmake_dir + 'Qt5Gui',
+        '-DQt5Test_DIR=' + qt_cmake_dir + 'Qt5Test',
+        '-DQt5QuickTest_DIR=' + qt_cmake_dir + 'Qt5QuickTest',
+        '-DENABLE_TESTS=' + str(enable_test),
         '-DCMAKE_SYSTEM_VERSION=' + sdk_version
     ]
     if not os.path.exists(build_dir):
@@ -232,38 +260,72 @@ def build(arch, toolset, sdk_version, config_str, project_path_under_current_pat
     # but will be outputted into x64/Beta folder (for Beta Only)
 
     print('Building projects in ' + config_str + '|' + arch)
-    qt_client_proj_path = build_dir + project_path_under_current_path
 
     msbuild = findMSBuild()
     if not os.path.isfile(msbuild):
         raise IOError('msbuild.exe not found. path=' + msbuild)
-    msbuild_args = getMSBuildArgs(arch, config_str, configuration_type, toolset)
+    msbuild_args = getMSBuildArgs(arch, config_str, toolset, configuration_type)
 
     if (force_option):
-        # force toolset
-        replace_vs_prop(qt_client_proj_path,
-                        'PlatformToolset',
-                        toolset)
-        # force unicode
-        replace_vs_prop(qt_client_proj_path,
-                        'CharacterSet',
-                        'Unicode')
-        # force sdk_version
-        replace_vs_prop(qt_client_proj_path,
-                        'WindowsTargetPlatformVersion',
-                        sdk_version)
-
-    build_project(msbuild, msbuild_args, qt_client_proj_path, vs_env_vars)
+        replace_necessary_vs_prop(project_path_under_current_path, toolset, sdk_version)
+
+    build_project(msbuild, msbuild_args, project_path_under_current_path, vs_env_vars)
+
+    # build test projects
+
+    if (enable_test):
+        build_tests_projects(arch, config_str, msbuild, vs_env_vars,
+                             toolset, sdk_version, force_option)
+
+def build_tests_projects(arch, config_str, msbuild, vs_env_vars, toolset,
+                         sdk_version, force_option=True):
+    print('Building test projects')
+
+    test_projects_application_list = [unit_test_project, qml_test_project]
+
+    # unit tests, qml tests
+    for project in test_projects_application_list:
+        if (force_option):
+            replace_necessary_vs_prop(project, toolset, sdk_version)
+
+        msbuild_args = getMSBuildArgs(arch, config_str, toolset)
+        build_project(msbuild, msbuild_args, project, vs_env_vars)
 
+def run_tests(mute_dring, output_to_files):
+    print('Running client tests')
+
+    test_exe_command_list = [qml_test_exe, unit_test_exe]
+
+    if mute_dring:
+        test_exe_command_list[0] += ' -mutedring'
+        test_exe_command_list[1] += ' -mutedring'
+    if output_to_files:
+        test_exe_command_list[0] += ' -o ' + this_dir + '\\x64\\test\\qml_tests.txt'
+        test_exe_command_list[1] += ' > ' + this_dir + '\\x64\\test\\unittests.txt'
+
+    test_result_code = 0
+
+    # make sure that the tests are rendered offscreen
+    os.environ["QT_QPA_PLATFORM"] = 'offscreen'
+    os.environ["QT_QUICK_BACKEND"] = 'software'
+    for test_exe_command in test_exe_command_list:
+        if (execute_cmd(test_exe_command, True)):
+            test_result_code = 1
+    sys.exit(test_result_code)
 
 def parse_args():
     ap = argparse.ArgumentParser(description="Client qt build tool")
+    subparser = ap.add_subparsers(dest="subparser_name")
+
     ap.add_argument(
         '-b', '--build', action='store_true',
         help='Build Qt Client')
     ap.add_argument(
         '-a', '--arch', default='x64',
         help='Sets the build architecture')
+    ap.add_argument(
+        '-wt', '--withtest', action='store_true',
+        help='Build Qt Client Test')
     ap.add_argument(
         '-d', '--deps', action='store_true',
         help='Build Deps for Qt Client')
@@ -283,6 +345,14 @@ def parse_args():
         '-q', '--qtver', default=qt_version_default,
         help='Sets the version of Qmake')
 
+    run_test = subparser.add_parser('runtests')
+    run_test.add_argument(
+        '-md', '--mutedring', action='store_true', default=False,
+        help='Avoid dring logs')
+    run_test.add_argument(
+        '-o', '--outputtofiles', action='store_true', default=False,
+        help='Output tests log into files')
+
     parsed_args = ap.parse_args()
 
     if parsed_args.toolset:
@@ -303,20 +373,28 @@ def main():
 
     parsed_args = parse_args()
 
+    enable_test = False
+
+    if parsed_args.withtest:
+        enable_test = True
+
+    if parsed_args.subparser_name == 'runtests':
+        run_tests(parsed_args.mutedring, parsed_args.outputtofiles)
+
     if parsed_args.deps:
         deps(parsed_args.arch, parsed_args.toolset, parsed_args.qtver)
 
     if parsed_args.build:
         build(parsed_args.arch, parsed_args.toolset, parsed_args.sdk,
-              'Release', '\\jami-qt.vcxproj', parsed_args.qtver)
+              'Release', jami_qt_project, parsed_args.qtver, enable_test)
 
     if parsed_args.beta:
         build(parsed_args.arch, parsed_args.toolset, parsed_args.sdk,
-              'Beta', '\\jami-qt.vcxproj', parsed_args.qtver)
+              'Beta', jami_qt_project, parsed_args.qtver, enable_test)
 
     if parsed_args.releasecompile:
         build(parsed_args.arch, parsed_args.toolset, parsed_args.sdk,
-              'ReleaseCompile', '\\jami-qt.vcxproj', parsed_args.qtver)
+              'ReleaseCompile', jami_qt_project, parsed_args.qtver, enable_test)
 
 
 if __name__ == '__main__':
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 0b8c64269..345c9484f 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -1,67 +1,209 @@
 find_package(Qt5 CONFIG REQUIRED QuickTest Test)
-find_package(GTest REQUIRED)
 
-enable_testing(true)
-set(QMLTEST_LIBS ${QML_LIBS} Qt5::QuickTest)
+if(MSVC)
+    # Download and unpack googletest at configure time
+    configure_file(googletest/CMakeLists.txt.in googletest-download/CMakeLists.txt)
+    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()
+
+    # Prevent overriding the parent project's compiler/linker
+    # settings on Windows
+    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)
+
+    option(gtest_disable_pthreads "Disable uses of pthreads in gtest." ON)
+else()
+    find_package(GTest REQUIRED)
+endif()
 
+enable_testing(true)
+set(QML_TEST_LIBS ${QML_LIBS} Qt5::QuickTest Qt5::Test)
 set(TESTS_INCLUDES
     ${CMAKE_SOURCE_DIR}/src
     ${CMAKE_SOURCE_DIR}/tests/qml
     ${CMAKE_SOURCE_DIR}/tests/unittests)
 
-include_directories(${CMAKE_CURRENT_SOURCE_DIR}
-    ${CMAKE_SOURCE_DIR}/src
-    ${LRC}/include/libringclient
-    ${LRC}/include)
-
 # Common jami files
 add_library(test_common_obj OBJECT ${COMMON_SOURCES} ${COMMON_HEADERS})
-target_link_libraries(test_common_obj ${QMLTEST_LIBS})
+target_link_libraries(test_common_obj ${QML_TEST_LIBS})
 target_compile_definitions(test_common_obj PRIVATE ENABLE_TESTS="ON")
 
+include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_SOURCE_DIR}/src)
+
 # QML tests
 add_executable(qml_tests
-    ${CMAKE_SOURCE_DIR}/tests/qml/main.cpp
-    ${QML_RESOURCES}
-    ${QML_RESOURCES_QML}
-    $<TARGET_OBJECTS:test_common_obj>)
+               ${CMAKE_SOURCE_DIR}/tests/qml/main.cpp
+               ${QML_RESOURCES}
+               ${QML_RESOURCES_QML}
+               $<TARGET_OBJECTS:test_common_obj>)
 
 target_link_libraries(qml_tests
-    ${QMLTEST_LIBS}
-    ${test_common_objects}
-    ${ringclient}
-    ${qrencode})
-
-target_include_directories(qml_tests PUBLIC
-    ${TESTS_INCLUDES}
-    ${LRC}/include/libringclient
-    ${LRC}/include)
+                      ${QML_TEST_LIBS}
+                      ${test_common_objects})
 
 target_compile_definitions(qml_tests PRIVATE ENABLE_TESTS="ON")
 
-add_test(NAME QmlTests COMMAND qml_tests -input ${PROJECT_SOURCE_DIR}/tests/qml/)
-
 # Unittests
+set(UNIT_TESTS_HEADER_FILES ${CMAKE_SOURCE_DIR}/tests/unittests/globaltestenvironment.h)
+
+set(UNIT_TESTS_SOURCE_FILES
+    ${CMAKE_SOURCE_DIR}/tests/unittests/main_unittest.cpp
+    ${CMAKE_SOURCE_DIR}/tests/unittests/accountadapter_unittest.cpp)
+
 add_executable(unittests
-    ${CMAKE_SOURCE_DIR}/tests/unittests/main_unittests.cpp
-    ${CMAKE_SOURCE_DIR}/tests/unittests/dummy_unittest.cpp
-    ${QML_RESOURCES}
-    ${QML_RESOURCES_QML}
-    $<TARGET_OBJECTS:test_common_obj>)
+               ${UNIT_TESTS_HEADER_FILES}
+               ${UNIT_TESTS_SOURCE_FILES}
+               ${QML_RESOURCES}
+               ${QML_RESOURCES_QML}
+               $<TARGET_OBJECTS:test_common_obj>)
 
 target_link_libraries(unittests
-    ${QMLTEST_LIBS}
-    ${test_common_objects}
-    ${ringclient}
-    ${qrencode}
-    gtest
-    pthread)
-
-target_include_directories(unittests PUBLIC
-    ${TESTS_INCLUDES}
-    ${LRC}/include/libringclient
-    ${LRC}/include)
+                      ${QML_TEST_LIBS}
+                      ${test_common_objects}
+                      gtest)
 
 target_compile_definitions(unittests PRIVATE ENABLE_TESTS="ON")
 
-add_test(NAME UnitTests COMMAND unittests)
+if(MSVC)
+    include_directories(${LRC_SRC_PATH}
+                        ${DRING_SRC_PATH})
+
+    # QML tests
+    target_link_libraries(qml_tests
+                          ${QTWRAPPER_LIB}
+                          ${RINGCLIENT_STATIC_LIB}
+                          ${QRENCODE_LIB}
+                          ${GNUTLS_LIB}
+                          ${DRING_LIB}
+                          ${WINDOWS_SYS_LIBS})
+
+    target_include_directories(qml_tests PUBLIC
+                               ${TESTS_INCLUDES}
+                               ${LRC_SRC_PATH}
+                               ${DRING_SRC_PATH})
+
+    # output test executable files into test folder
+    set_target_properties(qml_tests
+        PROPERTIES
+        RUNTIME_OUTPUT_DIRECTORY_RELEASE "${PROJECT_SOURCE_DIR}/x64/test"
+    )
+
+    # POST_BUILD steps
+
+    # check time stamp
+    set(TIME_STAMP_FILE ".deploy.stamp")
+    add_custom_command(TARGET qml_tests POST_BUILD
+                       WORKING_DIRECTORY "$<TARGET_FILE_DIR:qml_tests>"
+                       COMMAND ${CMAKE_COMMAND} -DTIME_STAMP_FILE=${TIME_STAMP_FILE}
+                               -P ${PROJECT_SOURCE_DIR}/cmake/time_stamp_check.cmake)
+
+    # daemon deploy
+    add_custom_command(TARGET qml_tests POST_BUILD
+                       WORKING_DIRECTORY "$<TARGET_FILE_DIR:qml_tests>"
+                       COMMAND ${CMAKE_COMMAND} -DTIME_STAMP_FILE=${TIME_STAMP_FILE}
+                               -DCOPY_TO_PATH=$<TARGET_FILE_DIR:qml_tests>
+                               -DDRING_PATH=${DRING}
+                               -DPROJECT_ROOT_DIR=${PROJECT_SOURCE_DIR}
+                               -P ${PROJECT_SOURCE_DIR}/cmake/windows_daemon_deploy.cmake)
+
+    # Qt deploy for test qmls
+    add_custom_command(TARGET qml_tests POST_BUILD
+                       WORKING_DIRECTORY "$<TARGET_FILE_DIR:qml_tests>"
+                       COMMAND ${CMAKE_COMMAND} -DTIME_STAMP_FILE=${TIME_STAMP_FILE}
+                               -DWIN_DEPLOY_QT_PATH=${CMAKE_PREFIX_PATH}/bin
+                               -DQML_SRC_DIR=${CMAKE_SOURCE_DIR}/tests/qml
+                               -DEXE_NAME=$<TARGET_FILE:qml_tests>
+                               -DOFF_SCREEN_PLUGIN_REQUESTED=TRUE
+                               -DCOPY_TO_PATH=$<TARGET_FILE_DIR:qml_tests>/platforms
+                               -DOFF_SCREEN_PLUGIN_PATH=${CMAKE_PREFIX_PATH}/plugins/platforms
+                               -P ${PROJECT_SOURCE_DIR}/cmake/windows_qt_deploy.cmake)
+
+    # Qt deploy for src qmls
+    add_custom_command(TARGET qml_tests POST_BUILD
+                       WORKING_DIRECTORY "$<TARGET_FILE_DIR:qml_tests>"
+                       COMMAND ${CMAKE_COMMAND} -DTIME_STAMP_FILE=${TIME_STAMP_FILE}
+                               -DWIN_DEPLOY_QT_PATH=${CMAKE_PREFIX_PATH}/bin
+                               -DQML_SRC_DIR=${SRC_DIR}
+                               -DEXE_NAME=$<TARGET_FILE:qml_tests>
+                               -P ${PROJECT_SOURCE_DIR}/cmake/windows_qt_deploy.cmake)
+
+    # create time stamp
+    add_custom_command(TARGET qml_tests POST_BUILD
+                       WORKING_DIRECTORY "$<TARGET_FILE_DIR:qml_tests>"
+                       COMMAND ${CMAKE_COMMAND} -DTIME_STAMP_FILE=${TIME_STAMP_FILE}
+                               -P ${PROJECT_SOURCE_DIR}/cmake/time_stamp_create.cmake)
+
+    # Unittests
+    target_link_libraries(unittests
+                          ${QTWRAPPER_LIB}
+                          ${RINGCLIENT_STATIC_LIB}
+                          ${QRENCODE_LIB}
+                          ${GNUTLS_LIB}
+                          ${DRING_LIB}
+                          ${WINDOWS_SYS_LIBS})
+
+    target_include_directories(unittests PUBLIC
+                               ${TESTS_INCLUDES}
+                               ${LRC_SRC_PATH}
+                               ${DRING_SRC_PATH})
+
+    # output test executable files into test folder
+    set_target_properties(unittests
+        PROPERTIES
+        RUNTIME_OUTPUT_DIRECTORY_RELEASE "${PROJECT_SOURCE_DIR}/x64/test"
+    )
+else()
+    include_directories(${LRC}/include/libringclient
+                        ${LRC}/include
+                        ${LIBNM_INCLUDE_DIRS}
+                        ${LIBNOTIFY_INCLUDE_DIRS}
+                        ${LIBGDKPIXBUF_INCLUDE_DIRS})
+
+    # QML tests
+    target_link_libraries(qml_tests
+                          ${ringclient}
+                          ${qrencode}
+                          ${X11}
+                          ${LIBNM_LIBRARIES}
+                          ${LIBNOTIFY_LIBRARIES}
+                          ${LIBGDKPIXBUF_LIBRARIES})
+
+    target_include_directories(qml_tests PUBLIC
+                               ${TESTS_INCLUDES}
+                               ${LRC}/include/libringclient
+                               ${LRC}/include)
+
+    add_test(NAME QmlTests COMMAND qml_tests -input ${PROJECT_SOURCE_DIR}/tests/qml/)
+
+    # Unittests
+    target_link_libraries(unittests
+                          ${ringclient}
+                          ${qrencode}
+                          pthread
+                          ${X11}
+                          ${LIBNM_LIBRARIES}
+                          ${LIBNOTIFY_LIBRARIES}
+                          ${LIBGDKPIXBUF_LIBRARIES})
+
+    target_include_directories(unittests PUBLIC
+                               ${TESTS_INCLUDES}
+                               ${LRC}/include/libringclient
+                               ${LRC}/include)
+
+    add_test(NAME UnitTests COMMAND unittests)
+endif()
\ No newline at end of file
diff --git a/tests/googletest/CMakeLists.txt.in b/tests/googletest/CMakeLists.txt.in
new file mode 100644
index 000000000..91cd0ef29
--- /dev/null
+++ b/tests/googletest/CMakeLists.txt.in
@@ -0,0 +1,15 @@
+cmake_minimum_required(VERSION 2.8.12)
+
+project(googletest-download NONE)
+
+include(ExternalProject)
+ExternalProject_Add(googletest
+                    GIT_REPOSITORY    https://github.com/google/googletest.git
+                    GIT_TAG           master
+                    SOURCE_DIR        "${CMAKE_CURRENT_BINARY_DIR}/googletest-src"
+                    BINARY_DIR        "${CMAKE_CURRENT_BINARY_DIR}/googletest-build"
+                    CONFIGURE_COMMAND ""
+                    BUILD_COMMAND     ""
+                    INSTALL_COMMAND   ""
+                    TEST_COMMAND      ""
+)
\ No newline at end of file
diff --git a/tests/qml/main.cpp b/tests/qml/main.cpp
index 64e872b7c..89d11c3ad 100644
--- a/tests/qml/main.cpp
+++ b/tests/qml/main.cpp
@@ -1,21 +1,203 @@
+/*
+ * Copyright (C) 2021 by Savoir-faire Linux
+ * Author: Albert Babí Oller <albert.babi@savoirfairelinux.com>
+ * Author: Mingrui Zhang <mingrui.zhang@savoirfairelinux.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "mainapplication.h"
+#include "qmlregister.h"
+#include "appsettingsmanager.h"
+#include "connectivitymonitor.h"
+#include "systemtray.h"
+#include "namedirectory.h"
+#include "qrimageprovider.h"
+#include "tintedbuttonimageprovider.h"
+#include "avatarimageprovider.h"
+
+#include "accountadapter.h"
+#include "avadapter.h"
+#include "calladapter.h"
+#include "contactadapter.h"
+#include "pluginadapter.h"
+#include "messagesadapter.h"
+#include "settingsadapter.h"
+#include "utilsadapter.h"
+#include "conversationsadapter.h"
+
+#include <atomic>
+
+#include <QScopedPointer>
 #include <QtQuickTest/quicktest.h>
 #include <QQmlEngine>
 #include <QQmlContext>
 
+#ifdef Q_OS_WIN
+#include <windows.h>
+#endif
+
+#if defined _MSC_VER && !COMPILE_ONLY
+#include <gnutls/gnutls.h>
+#endif
+
 class Setup : public QObject
 {
     Q_OBJECT
 
 public:
-    Setup() {}
+    Setup(bool muteDring = false)
+        : muteDring_(muteDring)
+    {}
+
+    void init()
+    {
+        connectivityMonitor_.reset(new ConnectivityMonitor(this));
+        settingsManager_.reset(new AppSettingsManager(this));
+        systemTray_.reset(new SystemTray(settingsManager_.get(), this));
+
+#if defined _MSC_VER && !COMPILE_ONLY
+        gnutls_global_init();
+#endif
+
+        std::atomic_bool isMigrating(false);
+        lrcInstance_.reset(
+            new LRCInstance(nullptr, nullptr, "", connectivityMonitor_.get(), muteDring_));
+        lrcInstance_->subscribeToDebugReceived();
+
+        auto downloadPath = settingsManager_->getValue(Settings::Key::DownloadPath);
+        lrcInstance_->dataTransferModel().downloadDirectory = downloadPath.toString() + "/";
+    }
+
+    void qmlEngineRegistration(QQmlEngine* engine)
+    {
+        // setup the adapters (their lifetimes are that of MainApplication)
+        auto callAdapter = new CallAdapter(systemTray_.get(), lrcInstance_.data(), this);
+        auto messagesAdapter = new MessagesAdapter(settingsManager_.get(),
+                                                   lrcInstance_.data(),
+                                                   this);
+        auto conversationsAdapter = new ConversationsAdapter(systemTray_.get(),
+                                                             lrcInstance_.data(),
+                                                             this);
+        auto avAdapter = new AvAdapter(lrcInstance_.data(), this);
+        auto contactAdapter = new ContactAdapter(lrcInstance_.data(), this);
+        auto accountAdapter = new AccountAdapter(settingsManager_.get(), lrcInstance_.data(), this);
+        auto utilsAdapter = new UtilsAdapter(systemTray_.get(), lrcInstance_.data(), this);
+        auto settingsAdapter = new SettingsAdapter(settingsManager_.get(),
+                                                   lrcInstance_.data(),
+                                                   this);
+        auto pluginAdapter = new PluginAdapter(lrcInstance_.data(), this);
+
+        // qml adapter registration
+        QML_REGISTERSINGLETONTYPE_POBJECT(NS_ADAPTERS, callAdapter, "CallAdapter");
+        QML_REGISTERSINGLETONTYPE_POBJECT(NS_ADAPTERS, messagesAdapter, "MessagesAdapter");
+        QML_REGISTERSINGLETONTYPE_POBJECT(NS_ADAPTERS, conversationsAdapter, "ConversationsAdapter");
+        QML_REGISTERSINGLETONTYPE_POBJECT(NS_ADAPTERS, avAdapter, "AvAdapter");
+        QML_REGISTERSINGLETONTYPE_POBJECT(NS_ADAPTERS, contactAdapter, "ContactAdapter");
+        QML_REGISTERSINGLETONTYPE_POBJECT(NS_ADAPTERS, accountAdapter, "AccountAdapter");
+        QML_REGISTERSINGLETONTYPE_POBJECT(NS_ADAPTERS, utilsAdapter, "UtilsAdapter");
+        QML_REGISTERSINGLETONTYPE_POBJECT(NS_ADAPTERS, settingsAdapter, "SettingsAdapter");
+        QML_REGISTERSINGLETONTYPE_POBJECT(NS_ADAPTERS, pluginAdapter, "PluginAdapter");
+
+        // TODO: remove these
+        QML_REGISTERSINGLETONTYPE_CUSTOM(NS_MODELS, AVModel, &lrcInstance_->avModel())
+        QML_REGISTERSINGLETONTYPE_CUSTOM(NS_MODELS, PluginModel, &lrcInstance_->pluginModel())
+        QML_REGISTERSINGLETONTYPE_CUSTOM(NS_HELPERS, UpdateManager, lrcInstance_->getUpdateManager())
+
+        // register other types that don't require injection(e.g. uncreatables, c++/qml singletons)
+        Utils::registerTypes();
+
+        engine->addImageProvider(QLatin1String("qrImage"), new QrImageProvider(lrcInstance_.get()));
+        engine->addImageProvider(QLatin1String("tintedPixmap"),
+                                 new TintedButtonImageProvider(lrcInstance_.get()));
+        engine->addImageProvider(QLatin1String("avatarImage"),
+                                 new AvatarImageProvider(lrcInstance_.get()));
+
+        engine->rootContext()->setContextProperty("ScreenInfo", &screenInfo_);
+        engine->rootContext()->setContextProperty("LRCInstance", lrcInstance_.get());
+
+        engine->setObjectOwnership(&lrcInstance_->avModel(), QQmlEngine::CppOwnership);
+        engine->setObjectOwnership(&lrcInstance_->pluginModel(), QQmlEngine::CppOwnership);
+        engine->setObjectOwnership(lrcInstance_->getUpdateManager(), QQmlEngine::CppOwnership);
+        engine->setObjectOwnership(lrcInstance_.get(), QQmlEngine::CppOwnership);
+        engine->setObjectOwnership(&NameDirectory::instance(), QQmlEngine::CppOwnership);
+    }
 
 public Q_SLOTS:
 
-    void qmlEngineAvailable(QQmlEngine *engine)
+    /*!
+     * Called once before qmlEngineAvailable.
+     */
+    void applicationAvailable()
+    {
+        init();
+    }
+
+    /*!
+     * Called when the QML engine is available. Any import paths, plugin paths,
+     * and extra file selectors will have been set on the engine by this point.
+     * This function is called once for each QML test file, so any arguments are
+     * unique to that test. For example, this means that each QML test file will
+     * have its own QML engine.
+     *
+     * This function can be used to register QML types and add import paths,
+     * amongst other things.
+     */
+    void qmlEngineAvailable(QQmlEngine* engine)
     {
         engine->addImportPath("qrc:/tests/qml");
+        qmlEngineRegistration(engine);
     }
+
+    /*!
+     * Called once right after the all test execution has finished. Use this
+     * function to clean up before everything is destroyed.
+     */
+    void cleanupTestCase() {}
+
+private:
+    QScopedPointer<LRCInstance> lrcInstance_;
+
+    QScopedPointer<ConnectivityMonitor> connectivityMonitor_;
+    QScopedPointer<AppSettingsManager> settingsManager_;
+    QScopedPointer<SystemTray> systemTray_;
+    ScreenInfo screenInfo_;
+
+    bool muteDring_ {false};
 };
 
-QUICK_TEST_MAIN_WITH_SETUP(testqml, Setup)
+int
+main(int argc, char** argv)
+{
+    bool muteDring {false};
+
+    // Remove "-mutedring" from argv, as quick_test_main_with_setup() will
+    // fail if given an invalid command-line argument.
+    auto end = std::remove_if(argv + 1, argv + argc, [](char* argv) {
+        return (strcmp(argv, "-mutedring") == 0);
+    });
+
+    if (end != argv + argc) {
+        muteDring = true;
+
+        // Adjust the argument count.
+        argc = std::distance(argv, end);
+    }
+
+    QTEST_SET_MAIN_SOURCE_PATH
+    Setup setup(muteDring);
+    return quick_test_main_with_setup(argc, argv, "qml_test", nullptr, &setup);
+}
+
 #include "main.moc"
diff --git a/tests/qml/src/tst_LocalAccount.qml b/tests/qml/src/tst_LocalAccount.qml
new file mode 100644
index 000000000..732cd2dec
--- /dev/null
+++ b/tests/qml/src/tst_LocalAccount.qml
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2021 by Savoir-faire Linux
+ * Author: Albert Babí Oller <albert.babi@savoirfairelinux.com>
+ * Author: Mingrui Zhang <mingrui.zhang@savoirfairelinux.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+import QtQuick 2.14
+import QtTest 1.2
+
+import net.jami.Adapters 1.0
+
+TestCase {
+    name: "Local Account Test"
+    when: windowShown
+
+    function test_initially_no_account() {
+        compare(UtilsAdapter.getAccountListSize(), 0)
+    }
+}
diff --git a/tests/qml/src/tst_PresenceIndicator.qml b/tests/qml/src/tst_PresenceIndicator.qml
new file mode 100644
index 000000000..9c4b4936e
--- /dev/null
+++ b/tests/qml/src/tst_PresenceIndicator.qml
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2021 by Savoir-faire Linux
+ * Author: Mingrui Zhang <mingrui.zhang@savoirfairelinux.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+import QtQuick 2.14
+import QtTest 1.2
+
+import net.jami.Models 1.0
+import net.jami.Constants 1.0
+
+import "qrc:/src/commoncomponents"
+
+PresenceIndicator {
+    id: uut
+
+    TestCase {
+        name: "Presence Indicator Color Test"
+
+        function test_color() {
+            compare(uut.color, JamiTheme.presenceGreen)
+
+            uut.status = Account.Status.TRYING
+
+            compare(uut.color, JamiTheme.unPresenceOrange)
+
+            uut.status = Account.Status.UNREGISTERED
+
+            compare(uut.color, JamiTheme.notificationRed)
+        }
+    }
+}
diff --git a/tests/qml/tst_test.qml b/tests/qml/tst_test.qml
deleted file mode 100644
index 26e4fa9bc..000000000
--- a/tests/qml/tst_test.qml
+++ /dev/null
@@ -1,10 +0,0 @@
-import QtQuick 2.14
-import QtTest 1.2
-
-TestCase {
-    name: "DummyTests"
-
-    function test_dummy() {
-        verify(true, "dummy test failed!")
-    }
-}
diff --git a/tests/unittests/accountadapter_unittest.cpp b/tests/unittests/accountadapter_unittest.cpp
new file mode 100644
index 000000000..3d8efdf06
--- /dev/null
+++ b/tests/unittests/accountadapter_unittest.cpp
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2021 by Savoir-faire Linux
+ * Author: Albert Babí Oller <albert.babi@savoirfairelinux.com>
+ * Author: Mingrui Zhang <mingrui.zhang@savoirfairelinux.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "globaltestenvironment.h"
+
+#include "mainapplication.h"
+#include "qmlregister.h"
+#include "appsettingsmanager.h"
+#include "connectivitymonitor.h"
+#include "systemtray.h"
+
+#include "accountadapter.h"
+
+#include <gtest/gtest.h>
+
+#ifdef Q_OS_WIN
+#include <windows.h>
+#endif
+
+#if defined _MSC_VER && !COMPILE_ONLY
+#include <gnutls/gnutls.h>
+#endif
+
+/*!
+ * Test fixture for AccountAdapter testing
+ */
+class AccountAdapterFixture : public ::testing::Test
+{
+public:
+    // Prepare unit test context. Called at
+    // prior each unit test execution
+    void SetUp() override
+    {
+        connectivityMonitor_.reset(new ConnectivityMonitor(nullptr));
+        settingsManager_.reset(new AppSettingsManager(nullptr));
+        systemTray_.reset(new SystemTray(settingsManager_.get(), nullptr));
+
+#if defined _MSC_VER && !COMPILE_ONLY
+        gnutls_global_init();
+#endif
+
+        std::atomic_bool isMigrating(false);
+        lrcInstance_.reset(
+            new LRCInstance(nullptr, nullptr, "", connectivityMonitor_.get(), muteDring));
+        lrcInstance_->subscribeToDebugReceived();
+
+        // setup the adapters (their lifetimes are that of MainApplication)
+        accountAdapter_.reset(
+            new AccountAdapter(settingsManager_.get(), lrcInstance_.data(), nullptr));
+    }
+
+    // Close unit test context. Called
+    // after each unit test ending
+    void TearDown() override {}
+
+    QScopedPointer<AccountAdapter> accountAdapter_;
+
+    QScopedPointer<LRCInstance> lrcInstance_;
+    QScopedPointer<ConnectivityMonitor> connectivityMonitor_;
+    QScopedPointer<AppSettingsManager> settingsManager_;
+    QScopedPointer<SystemTray> systemTray_;
+};
+
+/*!
+ * WHEN  There is no account initially.
+ * THEN  Account list should be empty.
+ */
+TEST_F(AccountAdapterFixture, InitialAccountListCheck)
+{
+    auto accountListSize = lrcInstance_->accountModel().getAccountList().size();
+
+    ASSERT_EQ(accountListSize, 0);
+}
\ No newline at end of file
diff --git a/tests/unittests/dummy_unittest.cpp b/tests/unittests/dummy_unittest.cpp
deleted file mode 100644
index 147a0e79b..000000000
--- a/tests/unittests/dummy_unittest.cpp
+++ /dev/null
@@ -1,7 +0,0 @@
-#include <gtest/gtest.h>
-
-TEST(DummyTest, TestDummy)
-{
-    EXPECT_EQ(1, 1);
-    ASSERT_EQ(0, 0);
-}
diff --git a/tests/unittests/globaltestenvironment.h b/tests/unittests/globaltestenvironment.h
new file mode 100644
index 000000000..b0cf45f66
--- /dev/null
+++ b/tests/unittests/globaltestenvironment.h
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2021 by Savoir-faire Linux
+ * Author: Mingrui Zhang <mingrui.zhang@savoirfairelinux.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+extern bool muteDring;
\ No newline at end of file
diff --git a/tests/unittests/main_unittest.cpp b/tests/unittests/main_unittest.cpp
new file mode 100644
index 000000000..45a025d9d
--- /dev/null
+++ b/tests/unittests/main_unittest.cpp
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2021 by Savoir-faire Linux
+ * Author: Albert Babí Oller <albert.babi@savoirfairelinux.com>
+ * Author: Mingrui Zhang <mingrui.zhang@savoirfairelinux.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "globaltestenvironment.h"
+
+#include <QApplication>
+#include <gtest/gtest.h>
+
+bool muteDring;
+
+int
+main(int argc, char* argv[])
+{
+    // Remove "-mutedring" from argv, as quick_test_main_with_setup() will
+    // fail if given an invalid command-line argument.
+    auto end = std::remove_if(argv + 1, argv + argc, [](char* argv) {
+        return (strcmp(argv, "-mutedring") == 0);
+    });
+
+    if (end != argv + argc) {
+        muteDring = true;
+
+        // Adjust the argument count.
+        argc = std::distance(argv, end);
+    }
+
+    QApplication a(argc, argv);
+    a.processEvents();
+    ::testing::InitGoogleTest(&argc, argv);
+    return RUN_ALL_TESTS();
+}
diff --git a/tests/unittests/main_unittests.cpp b/tests/unittests/main_unittests.cpp
deleted file mode 100644
index e2615987d..000000000
--- a/tests/unittests/main_unittests.cpp
+++ /dev/null
@@ -1,10 +0,0 @@
-#include <QApplication>
-#include <gtest/gtest.h>
-
-int main(int argc, char *argv[])
-{
-    QApplication a(argc, argv);
-    a.processEvents();
-    ::testing::InitGoogleTest(&argc, argv);
-    return RUN_ALL_TESTS();
-}
-- 
GitLab