diff --git a/pkg/innerring/blocktimer.go b/pkg/innerring/blocktimer.go
index 6a4dee801..ad69f207b 100644
--- a/pkg/innerring/blocktimer.go
+++ b/pkg/innerring/blocktimer.go
@@ -25,6 +25,10 @@ type (
 
 	newEpochHandler func()
 
+	containerEstimationStopper interface {
+		StopEstimation(p container.StopEstimationPrm) error
+	}
+
 	epochTimerArgs struct {
 		l *logger.Logger
 
@@ -32,8 +36,8 @@ type (
 
 		newEpochHandlers []newEpochHandler
 
-		cnrWrapper *container.Client // to invoke stop container estimation
-		epoch      epochState        // to specify which epoch to stop, and epoch duration
+		cnrWrapper containerEstimationStopper // to invoke stop container estimation
+		epoch      epochState                 // to specify which epoch to stop, and epoch duration
 
 		stopEstimationDMul uint32 // X: X/Y of epoch in blocks
 		stopEstimationDDiv uint32 // Y: X/Y of epoch in blocks
diff --git a/pkg/innerring/blocktimer_test.go b/pkg/innerring/blocktimer_test.go
new file mode 100644
index 000000000..e1a79c2a8
--- /dev/null
+++ b/pkg/innerring/blocktimer_test.go
@@ -0,0 +1,167 @@
+package innerring
+
+import (
+	"testing"
+
+	"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/container"
+	"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/event"
+	"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/logger/test"
+	"github.com/stretchr/testify/require"
+)
+
+func TestEpochTimer(t *testing.T) {
+	t.Parallel()
+	alphaState := &testAlphabetState{isAlphabet: true}
+	neh := &testNewEpochHandler{}
+	cnrStopper := &testContainerEstStopper{}
+	epochState := &testEpochState{
+		counter:  99,
+		duration: 10,
+	}
+	collectHandler := &testEventHandler{}
+	distributeHandler := &testEventHandler{}
+
+	args := &epochTimerArgs{
+		l:                  test.NewLogger(t, true),
+		alphabetState:      alphaState,
+		newEpochHandlers:   []newEpochHandler{neh.Handle},
+		cnrWrapper:         cnrStopper,
+		epoch:              epochState,
+		stopEstimationDMul: 2,
+		stopEstimationDDiv: 10,
+		collectBasicIncome: subEpochEventHandler{
+			handler:     collectHandler.Handle,
+			durationMul: 3,
+			durationDiv: 10,
+		},
+		distributeBasicIncome: subEpochEventHandler{
+			handler:     distributeHandler.Handle,
+			durationMul: 4,
+			durationDiv: 10,
+		},
+	}
+	et := newEpochTimer(args)
+	err := et.Reset()
+	require.NoError(t, err, "failed to reset timer")
+
+	et.Tick(100)
+	require.Equal(t, 0, neh.called, "invalid new epoch handler calls")
+	require.Equal(t, 0, cnrStopper.called, "invalid container stop handler calls")
+	require.Equal(t, 0, collectHandler.called, "invalid collect basic income calls")
+	require.Equal(t, 0, distributeHandler.called, "invalid distribute basic income calls")
+
+	et.Tick(101)
+	require.Equal(t, 0, neh.called, "invalid new epoch handler calls")
+	require.Equal(t, 1, cnrStopper.called, "invalid container stop handler calls")
+	require.Equal(t, 0, collectHandler.called, "invalid collect basic income calls")
+	require.Equal(t, 0, distributeHandler.called, "invalid distribute basic income calls")
+
+	et.Tick(102)
+	require.Equal(t, 0, neh.called, "invalid new epoch handler calls")
+	require.Equal(t, 1, cnrStopper.called, "invalid container stop handler calls")
+	require.Equal(t, 1, collectHandler.called, "invalid collect basic income calls")
+	require.Equal(t, 0, distributeHandler.called, "invalid distribute basic income calls")
+
+	et.Tick(103)
+	require.Equal(t, 0, neh.called, "invalid new epoch handler calls")
+	require.Equal(t, 1, cnrStopper.called, "invalid container stop handler calls")
+	require.Equal(t, 1, collectHandler.called, "invalid collect basic income calls")
+	require.Equal(t, 1, distributeHandler.called, "invalid distribute basic income calls")
+
+	var h uint32
+	for h = 104; h < 109; h++ {
+		et.Tick(h)
+		require.Equal(t, 0, neh.called, "invalid new epoch handler calls")
+		require.Equal(t, 1, cnrStopper.called, "invalid container stop handler calls")
+		require.Equal(t, 1, collectHandler.called, "invalid collect basic income calls")
+		require.Equal(t, 1, distributeHandler.called, "invalid distribute basic income calls")
+	}
+
+	et.Tick(109)
+	require.Equal(t, 1, neh.called, "invalid new epoch handler calls")
+	require.Equal(t, 1, cnrStopper.called, "invalid container stop handler calls")
+	require.Equal(t, 1, collectHandler.called, "invalid collect basic income calls")
+	require.Equal(t, 1, distributeHandler.called, "invalid distribute basic income calls")
+
+	et.Tick(110)
+	require.Equal(t, 1, neh.called, "invalid new epoch handler calls")
+	require.Equal(t, 1, cnrStopper.called, "invalid container stop handler calls")
+	require.Equal(t, 1, collectHandler.called, "invalid collect basic income calls")
+	require.Equal(t, 1, distributeHandler.called, "invalid distribute basic income calls")
+
+	et.Tick(111)
+	require.Equal(t, 1, neh.called, "invalid new epoch handler calls")
+	require.Equal(t, 2, cnrStopper.called, "invalid container stop handler calls")
+	require.Equal(t, 1, collectHandler.called, "invalid collect basic income calls")
+	require.Equal(t, 1, distributeHandler.called, "invalid distribute basic income calls")
+
+	et.Tick(112)
+	require.Equal(t, 1, neh.called, "invalid new epoch handler calls")
+	require.Equal(t, 2, cnrStopper.called, "invalid container stop handler calls")
+	require.Equal(t, 2, collectHandler.called, "invalid collect basic income calls")
+	require.Equal(t, 1, distributeHandler.called, "invalid distribute basic income calls")
+
+	et.Tick(113)
+	require.Equal(t, 1, neh.called, "invalid new epoch handler calls")
+	require.Equal(t, 2, cnrStopper.called, "invalid container stop handler calls")
+	require.Equal(t, 2, collectHandler.called, "invalid collect basic income calls")
+	require.Equal(t, 2, distributeHandler.called, "invalid distribute basic income calls")
+
+	for h = 114; h < 119; h++ {
+		et.Tick(h)
+		require.Equal(t, 1, neh.called, "invalid new epoch handler calls")
+		require.Equal(t, 2, cnrStopper.called, "invalid container stop handler calls")
+		require.Equal(t, 2, collectHandler.called, "invalid collect basic income calls")
+		require.Equal(t, 2, distributeHandler.called, "invalid distribute basic income calls")
+	}
+	et.Tick(120)
+	require.Equal(t, 2, neh.called, "invalid new epoch handler calls")
+	require.Equal(t, 2, cnrStopper.called, "invalid container stop handler calls")
+	require.Equal(t, 2, collectHandler.called, "invalid collect basic income calls")
+	require.Equal(t, 2, distributeHandler.called, "invalid distribute basic income calls")
+}
+
+type testAlphabetState struct {
+	isAlphabet bool
+}
+
+func (s *testAlphabetState) IsAlphabet() bool {
+	return s.isAlphabet
+}
+
+type testNewEpochHandler struct {
+	called int
+}
+
+func (h *testNewEpochHandler) Handle() {
+	h.called++
+}
+
+type testContainerEstStopper struct {
+	called int
+}
+
+func (s *testContainerEstStopper) StopEstimation(_ container.StopEstimationPrm) error {
+	s.called++
+	return nil
+}
+
+type testEpochState struct {
+	counter  uint64
+	duration uint64
+}
+
+func (s *testEpochState) EpochCounter() uint64 {
+	return s.counter
+}
+func (s *testEpochState) EpochDuration() uint64 {
+	return s.duration
+}
+
+type testEventHandler struct {
+	called int
+}
+
+func (h *testEventHandler) Handle(e event.Event) {
+	h.called++
+}