diff --git a/pom.xml b/pom.xml
index 38ad633..fd1508b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -412,6 +412,7 @@
core
jdk-platform-logging
slf4j
+ tinylog-provider
Mock Loggers
pom
diff --git a/tinylog-provider/pom.xml b/tinylog-provider/pom.xml
new file mode 100644
index 0000000..4b54f9d
--- /dev/null
+++ b/tinylog-provider/pom.xml
@@ -0,0 +1,98 @@
+
+
+
+ mock-loggers-tinylog-provider
+
+
+
+ maven-surefire-plugin
+ org.apache.maven.plugins
+
+
+ maven-failsafe-plugin
+ org.apache.maven.plugins
+
+
+ jacoco-maven-plugin
+ org.jacoco
+
+
+
+
+
+
+ annotations
+ org.jetbrains
+ provided
+
+
+ mock-loggers-core
+ io.github.vitalijr2.logging
+ 1.0.0
+
+
+ mockito-core
+ org.mockito
+ provided
+
+
+ tinylog-api
+ org.tinylog
+ provided
+ 2.7.0
+
+
+
+ junit-jupiter-engine
+ org.junit.jupiter
+ provided
+
+
+ junit-jupiter-params
+ org.junit.jupiter
+ provided
+
+
+ mockito-junit-jupiter
+ org.mockito
+ provided
+
+
+ Mock loggers for tinylog backed by Mockito.
+ 4.0.0
+ Mock loggers for tinylog
+
+ mock-loggers
+ io.github.vitalijr2.logging
+ 1.0.0
+
+
+
+
+
+
+ maven-invoker-plugin
+ org.apache.maven.plugins
+
+
+
+ run-its
+
+
+
\ No newline at end of file
diff --git a/tinylog-provider/src/main/java/io/github/vitalijr2/logging/mock/tinylog/MockLoggerProvider.java b/tinylog-provider/src/main/java/io/github/vitalijr2/logging/mock/tinylog/MockLoggerProvider.java
new file mode 100644
index 0000000..c98e5d4
--- /dev/null
+++ b/tinylog-provider/src/main/java/io/github/vitalijr2/logging/mock/tinylog/MockLoggerProvider.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2024 Vitalij Berdinskih
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.github.vitalijr2.logging.mock.tinylog;
+
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
+
+import io.github.vitalijr2.logging.mock.MockLoggerCleaner;
+import java.util.List;
+import org.tinylog.Level;
+import org.tinylog.format.MessageFormatter;
+import org.tinylog.provider.ContextProvider;
+import org.tinylog.provider.LoggingProvider;
+
+public class MockLoggerProvider implements LoggingProvider, MockLoggerCleaner {
+
+ static final LoggingProvider MOCK_INSTANCE;
+
+ static {
+ MOCK_INSTANCE = mock(LoggingProvider.class);
+ prepareMockInstance();
+ }
+
+ public MockLoggerProvider() {
+ subscribeToNotifications();
+ }
+
+ private static void prepareMockInstance() {
+ clearInvocations(MOCK_INSTANCE);
+ reset(MOCK_INSTANCE);
+ }
+
+ @Override
+ public List cleanAndReset() {
+ prepareMockInstance();
+ return List.of("tinylog");
+ }
+
+ @Override
+ public ContextProvider getContextProvider() {
+ return MOCK_INSTANCE.getContextProvider();
+ }
+
+ @Override
+ public Level getMinimumLevel() {
+ return MOCK_INSTANCE.getMinimumLevel();
+ }
+
+ @Override
+ public Level getMinimumLevel(String tag) {
+ return MOCK_INSTANCE.getMinimumLevel(tag);
+ }
+
+ @Override
+ public boolean isEnabled(int depth, String tag, Level level) {
+ return MOCK_INSTANCE.isEnabled(depth, tag, level);
+ }
+
+ @Override
+ public boolean isEnabled(String loggerClassName, String tag, Level level) {
+ return MOCK_INSTANCE.isEnabled(loggerClassName, tag, level);
+ }
+
+ @Override
+ public void log(int depth, String tag, Level level, Throwable exception, MessageFormatter formatter, Object obj,
+ Object... arguments) {
+ MOCK_INSTANCE.log(depth, tag, level, exception, formatter, obj, arguments);
+ }
+
+ @Override
+ public void log(String loggerClassName, String tag, Level level, Throwable exception, MessageFormatter formatter,
+ Object obj, Object... arguments) {
+ MOCK_INSTANCE.log(loggerClassName, tag, level, exception, formatter, obj, arguments);
+ }
+
+ @Override
+ public void shutdown() throws InterruptedException {
+ MOCK_INSTANCE.shutdown();
+ }
+
+}
diff --git a/tinylog-provider/src/test/java/io/github/vitalijr2/logging/mock/tinylog/MockLoggerProviderFastTest.java b/tinylog-provider/src/test/java/io/github/vitalijr2/logging/mock/tinylog/MockLoggerProviderFastTest.java
new file mode 100644
index 0000000..f67b0ac
--- /dev/null
+++ b/tinylog-provider/src/test/java/io/github/vitalijr2/logging/mock/tinylog/MockLoggerProviderFastTest.java
@@ -0,0 +1,185 @@
+package io.github.vitalijr2.logging.mock.tinylog;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isA;
+import static org.mockito.ArgumentMatchers.notNull;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
+import static org.mockito.Mockito.when;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
+import org.junit.jupiter.params.provider.ValueSource;
+import org.tinylog.Level;
+import org.tinylog.format.MessageFormatter;
+import org.tinylog.provider.NopContextProvider;
+
+
+@Tag("fast")
+class MockLoggerProviderFastTest {
+
+ private MockLoggerProvider provider;
+
+ @BeforeEach
+ void setUp() {
+ provider = new MockLoggerProvider();
+ }
+
+ @AfterEach
+ void tearDown() {
+ clearInvocations(MockLoggerProvider.MOCK_INSTANCE);
+ reset(MockLoggerProvider.MOCK_INSTANCE);
+ }
+
+ // Move to slow
+ @DisplayName("Clear and reset the mock instance")
+ @Test
+ void cleanAndReset() {
+ // when
+ provider.cleanAndReset();
+
+ // then
+ verifyNoInteractions(MockLoggerProvider.MOCK_INSTANCE);
+ }
+
+ @DisplayName("Get a context provider")
+ @Test
+ void getContextProvider() {
+ // given
+ when(MockLoggerProvider.MOCK_INSTANCE.getContextProvider()).thenReturn(new NopContextProvider());
+
+ // when and then
+ assertNotNull(provider.getContextProvider());
+
+ verify(MockLoggerProvider.MOCK_INSTANCE).getContextProvider();
+ }
+
+ @DisplayName("Get a minimum level")
+ @ParameterizedTest
+ @ValueSource(strings = {"TRACE", "DEBUG", "INFO", "WARN", "ERROR", "OFF"})
+ void getMinimumLevel(Level expectedlevel) {
+ // given
+ when(MockLoggerProvider.MOCK_INSTANCE.getMinimumLevel()).thenReturn(expectedlevel);
+
+ // when
+ var actualLevel = provider.getMinimumLevel();
+
+ // then
+ verify(MockLoggerProvider.MOCK_INSTANCE).getMinimumLevel();
+
+ assertEquals(expectedlevel, actualLevel);
+ }
+
+ @DisplayName("Get a minimum level for a classname")
+ @ParameterizedTest
+ @ValueSource(strings = {"TRACE", "DEBUG", "INFO", "WARN", "ERROR", "OFF"})
+ void getMinimumLevelByTagWithCustomLevel(Level expectedlevel) {
+ // given
+ when(MockLoggerProvider.MOCK_INSTANCE.getMinimumLevel(anyString())).thenReturn(expectedlevel);
+
+ // when
+ var actualLevel = provider.getMinimumLevel("test tag");
+
+ // then
+ verify(MockLoggerProvider.MOCK_INSTANCE).getMinimumLevel("test tag");
+
+ assertEquals(expectedlevel, actualLevel);
+ }
+
+ @DisplayName("Is it enabled?")
+ @ParameterizedTest
+ @CsvSource(value = {"TRACE,true", "TRACE,false", "DEBUG,true", "DEBUG,false", "INFO,true", "INFO,false", "WARN,true",
+ "WARN,false", "ERROR,true", "ERROR,false", "OFF,true", "OFF,false"})
+ void isEnabled(Level level, boolean expectedResult) {
+ // given
+ when(MockLoggerProvider.MOCK_INSTANCE.isEnabled(anyInt(), anyString(), isA(Level.class))).thenReturn(
+ expectedResult);
+
+ // when
+ var actualResult = provider.isEnabled(123, "test tag", level);
+
+ // then
+ verify(MockLoggerProvider.MOCK_INSTANCE).isEnabled(123, "test tag", level);
+
+ assertEquals(expectedResult, actualResult);
+ }
+
+ @DisplayName("Is it enabled for a classname?")
+ @ParameterizedTest
+ @CsvSource(value = {"TRACE,true", "TRACE,false", "DEBUG,true", "DEBUG,false", "INFO,true", "INFO,false", "WARN,true",
+ "WARN,false", "ERROR,true", "ERROR,false", "OFF,true", "OFF,false"}, nullValues = "N/A")
+ void isEnabledForClassname(Level level, boolean expectedResult) {
+ // given
+ when(MockLoggerProvider.MOCK_INSTANCE.isEnabled(anyString(), anyString(), isA(Level.class))).thenReturn(
+ expectedResult);
+
+ // when
+ var actualResult = provider.isEnabled("test.class", "test tag", level);
+
+ // then
+ verify(MockLoggerProvider.MOCK_INSTANCE).isEnabled("test.class", "test tag", level);
+
+ assertEquals(expectedResult, actualResult);
+ }
+
+ @DisplayName("Log")
+ @Test
+ void log() {
+ // when
+ provider.log(123, "test tag", Level.TRACE, new Throwable("test throwable"), mock(MessageFormatter.class), this, "a",
+ "b", "c");
+
+ // then
+ verify(MockLoggerProvider.MOCK_INSTANCE).log(eq(123), eq("test tag"), eq(Level.TRACE), isA(Throwable.class),
+ isA(MessageFormatter.class), notNull(), eq("a"), eq("b"), eq("c"));
+ }
+
+ @DisplayName("Log for a classname")
+ @Test
+ void logForClassname() {
+ // when
+ provider.log("test.class", "test tag", Level.TRACE, new Throwable("test throwable"), mock(MessageFormatter.class),
+ this, "a", "b", "c");
+
+ // then
+ verify(MockLoggerProvider.MOCK_INSTANCE).log(eq("test.class"), eq("test tag"), eq(Level.TRACE),
+ isA(Throwable.class), isA(MessageFormatter.class), notNull(), eq("a"), eq("b"), eq("c"));
+ }
+
+ @DisplayName("Shutdown")
+ @Test
+ void shutdown() throws InterruptedException {
+ // when
+ provider.shutdown();
+
+ // then
+ verify(MockLoggerProvider.MOCK_INSTANCE).shutdown();
+ }
+
+ @DisplayName("Shutdown with an exception")
+ @Test
+ void shutdownWithException() throws InterruptedException {
+ // given
+ doThrow(new InterruptedException("test")).when(MockLoggerProvider.MOCK_INSTANCE).shutdown();
+
+ // when and then
+ var exception = assertThrows(InterruptedException.class, () -> provider.shutdown());
+
+ assertEquals("test", exception.getMessage());
+ }
+
+}
\ No newline at end of file
diff --git a/tinylog-provider/src/test/java/io/github/vitalijr2/logging/mock/tinylog/MockLoggerProviderSlowTest.java b/tinylog-provider/src/test/java/io/github/vitalijr2/logging/mock/tinylog/MockLoggerProviderSlowTest.java
new file mode 100644
index 0000000..860ec30
--- /dev/null
+++ b/tinylog-provider/src/test/java/io/github/vitalijr2/logging/mock/tinylog/MockLoggerProviderSlowTest.java
@@ -0,0 +1,42 @@
+package io.github.vitalijr2.logging.mock.tinylog;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verifyNoInteractions;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+
+@Tag("slow")
+class MockLoggerProviderSlowTest {
+
+ private MockLoggerProvider provider;
+
+ @BeforeEach
+ void setUp() {
+ provider = new MockLoggerProvider();
+ }
+
+ @AfterEach
+ void tearDown() {
+ clearInvocations(MockLoggerProvider.MOCK_INSTANCE);
+ reset(MockLoggerProvider.MOCK_INSTANCE);
+ }
+
+ // Move to slow
+ @DisplayName("Clear and reset the mock instance")
+ @Test
+ void cleanAndReset() throws InterruptedException {
+ // when
+ provider.shutdown();
+ provider.cleanAndReset();
+
+ // then
+ verifyNoInteractions(MockLoggerProvider.MOCK_INSTANCE);
+ }
+
+}
\ No newline at end of file