Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Manual usage (i.e. not using Mockito extension) #140

Closed
perlun opened this issue May 17, 2022 · 5 comments
Closed

Manual usage (i.e. not using Mockito extension) #140

perlun opened this issue May 17, 2022 · 5 comments
Labels
question Further information is requested.

Comments

@perlun
Copy link

perlun commented May 17, 2022

Hi,

I discovered your project today when thinking about better ways to handle logging-based testing. Our current setup is roughly like this:

    public static Logger createLoggerMock() {
        Logger mock = createMock( Logger.class );

        // TODO: Clumsy workaround for https://github.com/mockito/mockito/issues/2644
        doAnswer( invocation -> {
            String format = invocation.getArgument( 0 );
            Object[] allArguments = invocation.getArguments();
            Object[] arguments = Arrays.copyOfRange( allArguments, 1, allArguments.length );
            logger.trace( format, arguments );
            return null;
        } ).when( mock )
                .trace( anyString(), (Object[])any() );

        doAnswer( answerVoid( (VoidAnswer3<String, Object, Object>)
                logger::debug
        ) ).when( mock )
                .debug( anyString(), any(), any() );

        doAnswer( invocation -> {
            String format = invocation.getArgument( 0 );
            Object[] allArguments = invocation.getArguments();
            Object[] arguments = Arrays.copyOfRange( allArguments, 1, allArguments.length );
            logger.info( format, arguments );
            return null;
        } ).when( mock )
                .info( anyString(), (Object[])any() );

        doAnswer( answerVoid( (VoidAnswer2<String, Object>)( message, arg ) -> {
            throw new RuntimeException( "Unexpected log warning: " + message + ", with argument " + arg );
        } ) ).when( mock )
                .warn( any(), ArgumentMatchers.<Object>any() );

        doAnswer( answerVoid( (VoidAnswer3<String, Object, Object>)( message, arg1, arg2 ) -> {
            throw new RuntimeException( "Unexpected log warning: " + message + ", with arguments: " + arg1 + ", " + arg2 );
        } ) ).when( mock )
                .warn( any(), ArgumentMatchers.<Object>any(), ArgumentMatchers.any() );

        doAnswer( answerVoid( (VoidAnswer3<String, Object, Object>)( message, arg1, arg2 ) -> {
            if ( arg2 instanceof Throwable ) {
                throw new RuntimeException( "Unexpected log error: " + message + " with argument " + arg1, (Throwable)arg2 );
            }
            else {
                throw new RuntimeException( "Unexpected log error: " + message + " with arguments [" + arg1 + ", " + arg2 + "]" );
            }
        } ) ).when( mock )
                .error( any(), ArgumentMatchers.<Object>any(), any() );

        return mock;
    }

In other words, we let debug() and trace() calls pass through to an underlying Logback implementation to ensure that this output can be seen when running tests. warn() and error() logging will cause assertion failures. (The same will go for info() calls because we set up our mocks to always fail on non-mocked methods).

However... moving all of this to use slf4j-mock is a bit of work, since we currently construct our mock/test objects in a @BeforeEach-method with JUnit. I'd really prefer an "imperative" approach where I manually construct the object being tested. This is part of what the @BeforeEach-method looks like at the moment:

    @BeforeEach
    void init() throws Exception {
        AuditLoggingManager auditLoggingManager = createMock( AuditLoggingManager.class );

        Logger logger = createLoggerMock();

        subject = new TimescaleDBEventPopulator(
                new JobConfig().withTypeName( "timescale-db-event-populator" ),
                logger,
                auditLoggingManager,
                serverManager,
                settingsManager,
                timescaleDBManager
        );

        // [...]
    }

One of the main motivations for looking at slf4j-mock is to get rid of this ugly manual logger injection here in the TimescaleDBEventPopulator constructor call. It would be really nice to just let slf4j-mock provide the SLF4J implementation to the class, so that we could remove this constructor parameter altogether, but preferably without having to move our whole setup to use a @Mock / @InjectMocks-based approach.

Sorry if what I'm trying to convey here might be a bit fuzzy. I think what I'm aiming for is mostly to minimize the impact on our existing test setup etc, if possible.

@perlun
Copy link
Author

perlun commented May 17, 2022

The Mockito issue being referenced: mockito/mockito#2644

@slawekjaranowski
Copy link
Member

You can manually create mock for logger and bind it to slf4j. look last example https://www.simplify4u.org/slf4j-mock/

@perlun
Copy link
Author

perlun commented May 24, 2022

Thanks, that could be an approach worth considering yes. 👍 How well would this work with tests executing in parallel though? (we use a setup like this)

    useTestNG() {
        parallel = 'classes'
    }

@slawekjaranowski
Copy link
Member

Thanks, that could be an approach worth considering yes. 👍 How well would this work with tests executing in parallel though? (we use a setup like this)

Please try - should works

@slawekjaranowski
Copy link
Member

I hope information was provided.
If you will have some other question please open new issue.

@slawekjaranowski slawekjaranowski added the question Further information is requested. label Sep 29, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested.
Development

No branches or pull requests

2 participants