diff --git a/GaudiCommonSvc/CMakeLists.txt b/GaudiCommonSvc/CMakeLists.txt
index 0fc495d0287ccee882584865286bc6c16370b4bc..089ea51263a85c116e8d92ca2d3ae51f7d70832e 100644
--- a/GaudiCommonSvc/CMakeLists.txt
+++ b/GaudiCommonSvc/CMakeLists.txt
@@ -70,3 +70,22 @@ if(GAUDI_USE_AIDA)
                                         src/HistogramPersistencySvc/HistogramPersistencySvc.cpp)
   target_link_libraries(GaudiCommonSvc PRIVATE AIDA::aida GaudiCommonSvcLib)
 endif()
+
+if(BUILD_TESTING)
+    gaudi_add_executable(test_AlgContextSvc
+        SOURCES
+            src/AlgContextSvc.cpp
+        LINK
+            Boost::headers
+            Catch2::Catch2WithMain
+            Gaudi::GaudiKernel
+        TEST
+    )
+    target_compile_definitions(test_AlgContextSvc PRIVATE BUILD_UNIT_TESTS)
+    catch_discover_tests(test_AlgContextSvc
+        TEST_PREFIX GaudiCommonSvc.
+        PROPERTIES
+            LABELS "Gaudi"
+            LABELS "Gaudi.GaudiCommonSvc"
+    )
+endif()
diff --git a/GaudiCommonSvc/src/AlgContextSvc.cpp b/GaudiCommonSvc/src/AlgContextSvc.cpp
index 769d2d435393144573e963ee436e0b947cd25f51..1ab934d57f877d7a5f24d810452687bf88ca903b 100644
--- a/GaudiCommonSvc/src/AlgContextSvc.cpp
+++ b/GaudiCommonSvc/src/AlgContextSvc.cpp
@@ -1,5 +1,5 @@
 /***********************************************************************************\
-* (c) Copyright 1998-2019 CERN for the benefit of the LHCb and ATLAS collaborations *
+* (c) Copyright 1998-2024 CERN for the benefit of the LHCb and ATLAS collaborations *
 *                                                                                   *
 * This software is distributed under the terms of the Apache version 2 licence,     *
 * copied verbatim in the file "LICENSE".                                            *
@@ -8,36 +8,71 @@
 * granted to it by virtue of its status as an Intergovernmental Organization        *
 * or submit itself to any jurisdiction.                                             *
 \***********************************************************************************/
-// ============================================================================
-// Include files
-// ============================================================================
-// Local
-// ============================================================================
-#include "AlgContextSvc.h"
-// ============================================================================
-// GaudiKernel
-// ============================================================================
-#include "GaudiKernel/ConcurrencyFlags.h"
-#include "GaudiKernel/IIncidentSvc.h"
-#include "GaudiKernel/ISvcLocator.h"
-#include "GaudiKernel/MsgStream.h"
+#include <GaudiKernel/ConcurrencyFlags.h>
+#include <GaudiKernel/IAlgContextSvc.h>
+#include <GaudiKernel/IAlgorithm.h>
+#include <GaudiKernel/IIncidentListener.h>
+#include <GaudiKernel/IIncidentSvc.h>
+#include <GaudiKernel/ISvcLocator.h>
+#include <GaudiKernel/MsgStream.h>
+#include <GaudiKernel/Service.h>
+#include <GaudiKernel/StatusCode.h>
+#include <boost/thread.hpp>
+#include <vector>
 
 // ============================================================================
-/** @file
- *  Implementation firl for class AlgContextSvc
+/** @class AlgContextSvc
+ *  Simple implementation of interface IAlgContextSvc
+ *  for Algorithm Context Service
  *  @author ATLAS Collaboration
- *  @author modified by Vanya BELYAEV ibelyaev@physics.syr.edu
- *  @author modified by Sami Kama
- *  @date 2017-03-17 (modified)
- */
-// ============================================================================
-/** Instantiation of a static factory class used by clients to create
- *  instances of this service
+ *  @author modified by Vanya BELYAEV ibelyaev@physics.sye.edu
+ *  @author incident listening  removed by Benedikt Hegner
+ *  @author S. Kama. Added multi-context incident based queueing to support
+ *          Serial-MT cases
+ *  @date 2007-03-07 (modified)
  */
+class AlgContextSvc : public extends<Service, IAlgContextSvc, IIncidentListener> {
+public:
+  /// set the currently executing algorithm ("push_back") @see IAlgContextSvc
+  StatusCode setCurrentAlg( IAlgorithm* a, const EventContext& context ) override;
+  /// remove the algorithm ("pop_back") @see IAlgContextSvc
+  StatusCode unSetCurrentAlg( IAlgorithm* a, const EventContext& context ) override;
+  /// accessor to current algorithm: @see IAlgContextSvc
+  IAlgorithm* currentAlg() const override;
+  /// get the stack of executed algorithms @see IAlgContextSvc
+  const IAlgContextSvc::Algorithms& algorithms() const override {
+    if ( !m_algorithms.get() ) {
+      static IAlgContextSvc::Algorithms empty;
+      return empty;
+    }
+    return *m_algorithms;
+  }
+
+public:
+  void handle( const Incident& ) override;
+
+public:
+  StatusCode initialize() override;
+  StatusCode start() override;
+  StatusCode finalize() override;
+
+public:
+  using extends::extends;
+
+private:
+  /// the stack of current algorithms
+  boost::thread_specific_ptr<IAlgContextSvc::Algorithms> m_algorithms;
+  /// pointer to Incident Service
+  SmartIF<IIncidentSvc> m_inc = nullptr;
+
+  Gaudi::Property<bool> m_check{ this, "Check", true, "Flag to perform more checks" };
+  Gaudi::Property<bool> m_bypassInc{ this, "BypassIncidents", false,
+                                     "Flag to bypass begin/endevent incident requirement" };
+  std::vector<int>      m_inEvtLoop;
+};
+
 DECLARE_COMPONENT( AlgContextSvc )
-// ============================================================================
-// standard initialization of the service
-// ============================================================================
+
 StatusCode AlgContextSvc::initialize() {
   // Initialize the base class
   StatusCode sc = Service::initialize();
@@ -89,9 +124,6 @@ StatusCode AlgContextSvc::start() {
   return sc;
 }
 
-// ============================================================================
-// standard finalization  of the service  @see IService
-// ============================================================================
 StatusCode AlgContextSvc::finalize() {
   if ( m_algorithms.get() && !m_algorithms->empty() ) {
     warning() << "Non-empty stack of algorithms #" << m_algorithms->size() << endmsg;
@@ -104,9 +136,7 @@ StatusCode AlgContextSvc::finalize() {
   // finalize the base class
   return Service::finalize();
 }
-// ============================================================================
-// set     the currently executing algorithm  ("push_back") @see IAlgContextSvc
-// ============================================================================
+
 StatusCode AlgContextSvc::setCurrentAlg( IAlgorithm* a, const EventContext& context ) {
   if ( !a ) {
     warning() << "IAlgorithm* points to NULL" << endmsg;
@@ -122,9 +152,7 @@ StatusCode AlgContextSvc::setCurrentAlg( IAlgorithm* a, const EventContext& cont
 
   return StatusCode::SUCCESS;
 }
-// ============================================================================
-// remove the algorithm                       ("pop_back") @see IAlgContextSvc
-// ============================================================================
+
 StatusCode AlgContextSvc::unSetCurrentAlg( IAlgorithm* a, const EventContext& context ) {
   // check whether thread-local algorithm list already exists
   // if not, create it
@@ -150,17 +178,13 @@ StatusCode AlgContextSvc::unSetCurrentAlg( IAlgorithm* a, const EventContext& co
   }
   return StatusCode::SUCCESS;
 }
-// ============================================================================
-/// accessor to current algorithm: @see IAlgContextSvc
-// ============================================================================
+
 IAlgorithm* AlgContextSvc::currentAlg() const {
   return ( m_algorithms.get() && !m_algorithms->empty() ) ? m_algorithms->back() : nullptr;
 }
-// ============================================================================
-// handle incident @see IIncidentListener
-// ============================================================================
+
 void AlgContextSvc::handle( const Incident& inc ) {
-  // some false sharing is possible but it should be negligable
+  // some false sharing is possible but it should be negligible
   auto currSlot = inc.context().slot();
   if ( currSlot == EventContext::INVALID_CONTEXT_ID ) { currSlot = 0; }
   if ( inc.type() == "BeginEvent" ) {
@@ -181,7 +205,71 @@ void AlgContextSvc::handle( const Incident& inc ) {
   //   }
   // }
 }
-// ============================================================================
-// ============================================================================
-/// The END
-// ============================================================================
+
+// From here on, we have unit tests.
+#if defined( BUILD_UNIT_TESTS )
+#  if __has_include( <catch2/catch.hpp>)
+// Catch2 v2
+#    include <catch2/catch.hpp>
+#  else
+// Catch2 v3
+#    include <catch2/catch_test_macros.hpp>
+#  endif
+
+#  include <Gaudi/Algorithm.h>
+
+namespace mock {
+  struct ServiceLocator : implements<ISvcLocator> {
+    std::list<IService*> m_services;
+    SmartIF<IService>    m_null_svc;
+
+    const std::list<IService*>& getServices() const override { return m_services; }
+    bool                        existsService( std::string_view /* name */ ) const override { return false; }
+    SmartIF<IService>&          service( const Gaudi::Utils::TypeNameString& /* typeName */,
+                                         const bool /* createIf */ ) override {
+      return m_null_svc;
+    }
+  };
+  struct Algorithm : Gaudi::Algorithm {
+    using Gaudi::Algorithm::Algorithm;
+    StatusCode execute( const EventContext& ) const override { return StatusCode::SUCCESS; }
+  };
+} // namespace mock
+
+TEST_CASE( "AlgContextSvc basic operations" ) {
+  SmartIF<ISvcLocator> svcLoc{ new mock::ServiceLocator };
+  AlgContextSvc        acs{ "AlgContextSvc", svcLoc };
+  REQUIRE( acs.setProperty( "BypassIncidents", true ).isSuccess() ); // do not try to invoke incident svc
+
+  // check that algorithms() never returns a nullptr
+  // (see https://gitlab.cern.ch/gaudi/Gaudi/-/issues/304)
+  auto empty_algorithms = &acs.algorithms();
+  REQUIRE( empty_algorithms != nullptr );
+  CHECK( empty_algorithms->empty() );
+
+  mock::Algorithm alg{ "dummy", svcLoc };
+  EventContext    ctx;
+
+  // add an algorithm
+  REQUIRE( acs.setCurrentAlg( &alg, ctx ).isSuccess() );
+  {
+    auto algorithms = &acs.algorithms();
+
+    // what we get before adding the first algorithm is a dummy static instance
+    // see https://gitlab.cern.ch/gaudi/Gaudi/-/issues/304#note_7930366
+    CHECK( empty_algorithms != algorithms );
+
+    REQUIRE( algorithms != nullptr );
+    REQUIRE( algorithms->size() == 1 );
+    CHECK( algorithms->at( 0 ) == &alg );
+  }
+
+  // removing the algorithm results in a an empty list
+  REQUIRE( acs.unSetCurrentAlg( &alg, ctx ).isSuccess() );
+  {
+    auto algorithms = &acs.algorithms();
+    REQUIRE( algorithms != nullptr );
+    REQUIRE( algorithms->empty() );
+  }
+}
+#endif
diff --git a/GaudiCommonSvc/src/AlgContextSvc.h b/GaudiCommonSvc/src/AlgContextSvc.h
deleted file mode 100644
index 862c6b5d612c690fdc2c9f590b687b7a31662873..0000000000000000000000000000000000000000
--- a/GaudiCommonSvc/src/AlgContextSvc.h
+++ /dev/null
@@ -1,89 +0,0 @@
-/***********************************************************************************\
-* (c) Copyright 1998-2019 CERN for the benefit of the LHCb and ATLAS collaborations *
-*                                                                                   *
-* This software is distributed under the terms of the Apache version 2 licence,     *
-* copied verbatim in the file "LICENSE".                                            *
-*                                                                                   *
-* In applying this licence, CERN does not waive the privileges and immunities       *
-* granted to it by virtue of its status as an Intergovernmental Organization        *
-* or submit itself to any jurisdiction.                                             *
-\***********************************************************************************/
-// ============================================================================
-#ifndef GAUDISVC_ALGCONTEXTSVC_H
-#define GAUDISVC_ALGCONTEXTSVC_H 1
-// ============================================================================
-// Include files
-// ============================================================================
-#include <vector>
-// ============================================================================
-// GaudiKernel
-// ============================================================================
-#include "GaudiKernel/IAlgContextSvc.h"
-#include "GaudiKernel/IAlgorithm.h"
-#include "GaudiKernel/IIncidentListener.h"
-#include "GaudiKernel/Service.h"
-#include "GaudiKernel/StatusCode.h"
-#include <boost/thread.hpp>
-
-// ============================================================================
-// Forward declarations
-// ============================================================================
-class IIncidentSvc;
-// ============================================================================
-/** @class AlgContexSvc
- *  Simple implementation of interface IAlgContextSvc
- *  for Algorithm Context Service
- *  @author ATLAS Collaboration
- *  @author modified by Vanya BELYAEV ibelyaev@physics.sye.edu
- *  @author incident listening  removed by Benedikt Hegner
- *  @author S. Kama. Added multi-context incident based queueing to support
- *          Serial-MT cases
- *  @date 2007-03-07 (modified)
- */
-class AlgContextSvc : public extends<Service, IAlgContextSvc, IIncidentListener> {
-public:
-  /// set the currently executing algorithm ("push_back") @see IAlgContextSvc
-  StatusCode setCurrentAlg( IAlgorithm* a, const EventContext& context ) override;
-  /// remove the algorithm ("pop_back") @see IAlgContextSvc
-  StatusCode unSetCurrentAlg( IAlgorithm* a, const EventContext& context ) override;
-  /// accessor to current algorithm: @see IAlgContextSvc
-  IAlgorithm* currentAlg() const override;
-  /// get the stack of executed algorithms @see IAlgContextSvc
-  const IAlgContextSvc::Algorithms& algorithms() const override { return *m_algorithms; }
-
-public:
-  /// handle incident @see IIncidentListener
-  void handle( const Incident& ) override;
-
-public:
-  /// standard initialization of the service @see IService
-  StatusCode initialize() override;
-  StatusCode start() override;
-  /// standard finalization  of the service  @see IService
-  StatusCode finalize() override;
-
-public:
-  using extends::extends;
-
-private:
-  // default/copy constructor & asignment are deleted
-  AlgContextSvc()                                  = delete;
-  AlgContextSvc( const AlgContextSvc& )            = delete;
-  AlgContextSvc& operator=( const AlgContextSvc& ) = delete;
-
-private:
-  // the stack of current algorithms
-  boost::thread_specific_ptr<IAlgContextSvc::Algorithms> m_algorithms; ///< the stack of current algorithms
-  // pointer to Incident Service
-  SmartIF<IIncidentSvc> m_inc = nullptr; ///< pointer to Incident Service
-
-  Gaudi::Property<bool> m_check{ this, "Check", true, "Flag to perform more checks" };
-  Gaudi::Property<bool> m_bypassInc{ this, "BypassIncidents", false,
-                                     "Flag to bypass begin/endevent incident requirement" };
-  std::vector<int>      m_inEvtLoop;
-};
-
-// ============================================================================
-// The END
-// ============================================================================
-#endif // GAUDISVC_ALGCONTEXTSVC_H