Giter Site home page Giter Site logo

mocks's Introduction

Mocks

This project is a proof-of-concept prototype of the Mockito framework. This library offers the basic functionality of the Mockito framework.

Build Status codecov Quality Gate Status Code Smells

Dependency

This project is distributed via JitPack. Register a JitPack repository at your pom.xml:

<repositories>
    <repository>
        <id>jitpack.io</id>
        <url>https://jitpack.io</url>
    </repository>
</repositories

And add the following dependency:

<dependency>
    <groupId>com.github.riguron</groupId>
    <artifactId>Mocks</artifactId>
    <version>v1.0</version>
</dependency>

Features

The public API is represented by Mocks class. Add the following static method in your test class in order to access Mocks API in a concise manner:

import static io.riguron.mocks.Mocks.*;

Mocking

To create a configurable mock object, make use of mock method:

UserRepository userRepository = mock(UserRepository.class); 

Configuring a mock

To configure mock behaviour, use when method:

UserRepository userRepository = mock(UserRepository.class);
when(userRepository.count()).thenReturn(5);
assertEquals(5, userRepository.count());

Arguments

To configure a behaviour of a method with arguments on mock object, use matchers provided by ArgumentMatchers class:

UserRepository userRepository = mock(UserRepository.class);
when(userRepository.findNameById(eq(1))).thenReturn("John Doe");
assertEquals("John Doe", userRepository.findNameById(1));

By specifying matchers, you set the arguments that must be passed to the method invocation so that the configured method call result is returned. In above example, you configure mock so that "John Doe" is returned if method's called with argument of value 1. In case of providing any arguments other than 1, nothing will be returned.

That's true for the arbitrary argument count: the return logic of your method will be reached only if each actual invocation argument matches the corresponding matcher specified in the invocation configuration.

Note: unlike the Mockito, Mocks library always accepts a matcher as an argument during mock configuration.

Generalized arguments

If the invocation arguments are not known at the compilation time or for some reason you don't want to set exact expected arguments, make use of matchers that accept any possible arguments:

Random random = new Random();
UserRepository userRepository = mock(UserRepository.class);
when(userRepository.findNameById(anyInt())).thenReturn("Name");
assertEquals("Name", userRepository.findNameById(random.nextInt()));

No matter what argument is passed to findNameById method, it will always return "Name". Such a behaviour is achieved by using anyInt matcher that configures mock not to check the argument value, i.e always match the argument.

Custom return logic

Sometimes it is not enough just to return a fixed value. For example, you may need to access the arguments of the invocation. To set a computable return result, use thenAnswer method:

UserRepository userRepository = mock(UserRepository.class);
when(userRepository.findNameById(eq(1))).thenAnswer(invocation -> "User-" + invocation.getArgument(0));
assertEquals("User-1", userRepository.findNameById(1));

Answer interface represents the logic of the computation of the mocked method execution result, allowing for accessing invocation arguments and other data involved in the invocation.

Mocking void methods

To mock a void method, make use of prefix-style mocking:

UserRepository userRepository = mock(UserRepository.class);
doAnswer(mockInvocation -> {
    System.out.println("Arguments: " + Arrays.toString(mockInvocation.getArguments()));
    return null;
}).when(userRepository).updateName(1, "Name");
userRepository.findNameById(5);

This test will produce the following output:

Arguments: [1, Name]

Throwing exceptions

In order to configure a method to throw an exception, use either thenThrow or doThrow depending on whether your method is void:

UserRepository userRepository = mock(UserRepository.class);
when(userRepository.count()).thenThrow(UnsupportedOperationException::new);
assertThrows(UnsupportedOperationException.class, userRepository::count);

For the void methods, make use of doThrow methods:

UserRepository userRepository = mock(UserRepository.class);
doThrow(UnsupportedOperationException::new).when(userRepository).updateName(eq(1), eq("Name"));
assertThrows(UnsupportedOperationException.class, () -> userRepository.updateName(1, "Name"));

Verification

Verification allows for checking whether the method was executed a certain amount of times (including 0). It may be useful, for example, when you have no control on method return result and can't assert the result of its execution. It's also helpful for asserting the execution of the void methods, since there's no other elegant way to do it.

To access all verification features, declare the following import into your class file:

import static io.riguron.mocks.verify.VerificationModes.*;

Asserting the number of invocations

UserRepository userRepository = mock(UserRepository.class);
userRepository.count();
userRepository.count();
verify(userRepository, times(2)).count();

At line 3, we assert that method count was called two times so far. To assert that method was executed only once, the following syntax:

verify(userRepository, times(1)).count();

may be simplified to:

verify(userRepository).count();

Verifying with arguments

There's no special syntax for verifying invocations with the arguments. However, the assertion must be configured with use of matchers, as in previous exaxmples:

UserRepository userRepository = mock(UserRepository.class);
userRepository.findNameById(1);
verify(userRepository).findNameById(eq(1));

You may define separate assertions for the distinct arguments:

UserRepository userRepository = mock(UserRepository.class);
userRepository.findNameById(1);
userRepository.findNameById(2);
verify(userRepository).findNameById(eq(1));
verify(userRepository).findNameById(eq(2));

Any-style syntax is also acceptable:

UserRepository userRepository = mock(UserRepository.class);
userRepository.findNameById(1);
userRepository.findNameById(5);
verify(userRepository, times(2)).findNameById(anyInt());

Asserting the absence of invocations

If you need to assert that neither of mock methods was called, take advantage of verifyNoInteractions method:

UserRepository userRepository = mock(UserRepository.class);
verifyNoInteractions(userRepository);

This assertion will pass only if none of UserRepository methods were called.

Reseting method calls

Sometimes there's a need to make a verification from a clean sheet, ignoring any previous invocations. To reset any data about method calls on a specific mock, use reset method:

UserRepository userRepository = mock(UserRepository.class);
userRepository.count();
userRepository.count();
verify(userRepository, times(2)).count();
reset(userRepository);
verifyNoInteractions(userRepository);

Information about method calls on the mock was reset at the penultimate line, so the assertion at the last time can be passed.

Advanced matching

This section elaborates on the argument matching and provides the information on complex matching cases.

Primitive any-style matchers

If a method's parameter is primitive, you must use primitive any-style matcher and not the generalized one. The latter is intended for configuring reference-type parameters (String, List or any non-primitive types) and not the primitive ones.

So, always use any[int/double/etc] matchers for primitive parameters, and not just any matcher. Thus, for the given method:

String findNameById(int id);

The following configuration will fail at the runtime:

UserRepository userRepository = mock(UserRepository.class);
when(userRepository.findNameById(any())).thenReturn("Result");

To fix the issue, replace any with anyInt:

UserRepository userRepository = mock(UserRepository.class);
when(userRepository.findNameById(anyInt())).thenReturn("Result");
assertEquals("Result", userRepository.findNameById(100));

Custom matchers

Custom matchers, i.e predicates, are intended for the complex matching cases, where the matching loggic differs from plain value comparation. Custom matchers are passed via argThat method:

UserRepository userRepository = mock(UserRepository.class);
when(userRepository.getAge(argThat(new ArgumentMatcher<String>() {
    @Override
    public boolean matches(String x) {
        return x.length() == 8 && x.contains("Name");
    }
}))).thenReturn(5);
assertEquals(5, userRepository.getAge("UserName"));

Lambda-style is acceptable as well:

UserRepository userRepository = mock(UserRepository.class);
when(userRepository.getAge(argThat(x -> x.length() == 8 && x.contains("Name")))).thenReturn(5);
assertEquals(5, userRepository.getAge("UserName"));

Mocking limitations

An entire idea of mocking is based on Proxy pattern. Mocks implement it using subclassing your original class in runtime to intercept its methods. Therefore, Mocks is not able to mock a component that is not subclassable / overridable. Thus, the following components can not be mocked or configured:

  • Final and private methods
  • Final classes
  • Methods that are not accessible from the test class
  • Static methods, regardless of the visibility

Also, you can't mock equals, hashCode and toString methods since they're involved in the internal Mocks implementation. Obviously, you can't mock fields (and should never do it). Constructors can not be mocked or configured as well.

However, you are free to mock:

  • Ordinary classes
  • Abstract classes and interfaces
  • Classes without the default empty constructor
  • Classes with a private constructor
  • Local classes
  • Non-static inner classes

Exception handling

Mocks framework is pretty strict in terms of syntax and aims to report any API misuses. This section elaborates on exceptions that may arise while using Mocks features.

UnfinishedStubbingException

Indicates that the method behaviour is configured improperly, namely lacking the essential part. Consider the following example:

UserRepository repository = mock(UserRepository.class);
when(repository.getAge(any()));
assertEquals(5, repository.getAge("Name"));

As you see, when expression is not finished and lacks the specification of the return result, which is mandatory part of the behaviour configuration.

mocks's People

Contributors

jolice avatar

Stargazers

 avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.