Remock is a small library which helps integration testing spring applications. It is heavily inspired by springockito-annotations which unfortunately appears not be maintained anymore.
Remock allows you to easily:
- Replace any spring bean with a Mockito mock.
- Replace any spring bean with a Mockito spy.
- Replace any spring bean with a different implementation.
- Reject any spring bean from being created without replacing it.
Note: Remock only works with Spring 5.0 (or later) and Java 8 (or later).
Follow this link to maven central. Choose the latest version, and choose a build system.
tl;dr: Annotate your Spring test-classes with @BootstrapWith(RemockBootstrapper.class)
. Then you can annotate the
test-class or field with @Reject
, @ReplaceWithImpl
, @ReplaceWithMock
or @WrapWithSpy
.
The following code will replace SomeDependency
with a Mockito mock. Since @ReplaceWithMock
is a meta-annotation for
@Autowired
, it will also automatically be injected into the test. Usage:
@RunWith(SpringJUnit4ClassRunner.class)
@BootstrapWith(RemockBootstrapper.class)
@ContextConfiguration(classes = SomeService.class)
public class ReplaceWithMockTest {
@ReplaceWithMock
public SomeDependency someDependency;
@Inject
public SomeService someService;
@Test
public void test() {
when(someDependency.method()).thenReturn(42);
assertEquals(21, someService.getHalf());
assertTrue(isMock(someDependency));
}
}
@ReplaceWithMock
can also be annotated directly test-class where it is repeatable.
The following code will wrap the original SomeDependency
instance with a Mockito spy. Since @WrapWithSpy
is a
meta-annotation for @Autowired
, it will also automatically be injected into the test. Usage:
@RunWith(SpringJUnit4ClassRunner.class)
@BootstrapWith(RemockBootstrapper.class)
@ContextConfiguration(classes = SomeService.class)
public class ReplaceWithMockTest {
@WrapWithSpy
public SomeDependency someDependency;
@Inject
public SomeService someService;
@Test
public void test() {
someService.getHalf()
verify(someDependency).method();
}
}
@WrapWithSpy
can also be annotated directly on the test-class where it is repeatable.
The following code will replace ServiceImpl
with ServiceMock
. Since @ReplaceWithImpl is a meta-annotation the
bean will be auto injected. Usage:
@RunWith(SpringJUnit4ClassRunner.class)
@BootstrapWith(RemockBootstrapper.class)
@ContextConfiguration(classes = {ServiceImpl.class})
public static class ReplaceWithImplAnnotatedOnFieldTest {
@ReplaceWithImpl(value = ServiceImpl.class, with = ServiceMock.class)
public Service service;
@Test
public void test() {
assertEquals(ServiceMock.class, service.getClass());
}
}
@ReplaceWithImpl
can also be annotated directly on the test-class where it is repeatable.
The following code will reject any bean definitions of the type SomeDangerousService
from being defined in
Spring's bean factory.
@RunWith(SpringJUnit4ClassRunner.class)
@BootstrapWith(RemockBootstrapper.class)
@Reject(SomeDangerousService.class)
@ContextConfiguration(classes = SomeService.class)
public class RejectTest {
@Test
public void test() {
/* SomeDangerousService does not exist */
}
}
This is a bit out of the ordinary, but it's quite powerful. This is particularly useful when you inject List or Maps of an interface or a superclass and want to remove some beans from the bean factory.
Another use-case is for controlling which beans are defined and lifecycled when a @ComponentScan
is used.
@Reject
can also be used for a field, though I cannot see a reason why you would. @Reject
is repeatable.
Often tests require the same mocks. Remock allows you to easily group mocks, either by specifying the remock-annotations
on a superclass, or by using the @RemockContextConfiguration
.
@RunWith(SpringJUnit4ClassRunner.class)
@BootstrapWith(RemockBootstrapper.class)
@ContextConfiguration(classes = {SomeServiceWithDependencies.class, SomeDependency.class})
@RemockContextConfiguration(MyRemockConfig.class)
public class RemockContextConfigurationTest {
@Inject
private SomeServiceWithDependencies someServiceWithDependencies;
@Test
public void test() {
isMock(someServiceWithDependencies.getDependency());
}
@ReplaceWithMock(SomeServiceWithDependencies.class)
@Reject(SomeDependency.class)
public static class MyRemockConfig {
}
}
For more detailed examples see the test cases.
A core goal of Remock is to be as fast as possible. Remock will therefore by default set all the beans to be lazily initialized.
This is to enable you to point the @ContextConfigration
at e.g. a class that does component scanning.
However, the following exceptions apply:
- Remock will not set beans with the role ROLE_INFRASTRUCTURE to lazy with the assumption that those beans are required to correctly setup the spring context.
- Remock will not set beans directly defined in the
@ContextConfiguration
to lazy. The rationale for this is that by adding a class to@ContextConfiguration
you obviously need it in your test.
Anything else will be lazy unless you use:
Annotating your test with @DisableLazyInit
disables the lazy init for all classes.
@RunWith(SpringJUnit4ClassRunner.class)
@BootstrapWith(RemockBootstrapper.class)
@ContextConfiguration(classes = SomeClassAnnotatedWith_Configuration.class})
@DisableLazyInit
public class MyTest {
@Test
public void test() {
/* ... */
}
}
If you do not want everything to be eagerly initialized, you can specify which beans you want to disable lazy init for.
@DisableLazyInit(value = EagerService.class, beanName = "eagerBean")
Trying to use Spring's @WebAppConfiguration
in conjunction with Remock will fail with an error message like:
java.lang.IllegalStateException: Configuration error: found multiple declarations of @BootstrapWith on test class
[org.example.MyClass] with values [class org.springframework.test.context.web.WebTestContextBootstrapper, class
no.saua.remock.RemockBootstrapper]
Instead you'll have to use the equivalent @RemockWebAppTest
annotation:
@RunWith(SpringJUnit4ClassRunner.class)
@BootstrapWith(RemockBootstrapper.class)
@ContextConfiguration(classes = WebAppTest.MyController.class)
@RemockWebAppTest
public class WebAppTest extends CommonTest {
@Inject
private WebApplicationContext context;
@Test
public void meh() throws Exception {
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(context).build();
MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.get("/abc")).andReturn();
String contentAsString = mvcResult.getResponse().getContentAsString();
assertEquals("abcxyz", contentAsString);
}
@Controller
public static class MyController {
@RequestMapping("/abc")
@ResponseBody
public String abc() {
return "abcxyz";
}
}
}
Note that lazy loading does not work with spring mvc @Controller
s. They will all be eagerly instantiated.
The big difference between Springockito and Remock is whether or not the original implementation lives inside springs
bean factory. While Springockito will use @Primary on all mocked/spied beans, thus taking precedence over the originals,
they will still be injected if you @Inject List<InterfaceOrSuperClass>
, or @Inject Map<String, InterfaceOrSuperClass>
.
Remock takes a different approach. It takes control over Spring's bean factory and downright rejects adding the bean definitions of beans it knows should be mocked or rejected.
With regards to bean factories Remock cannot reject them because it cannot know which class the bean factory will create until after Spring has resolved the bean factory. Remock will instead ensure that Spring never uses the factory-method by returning a bogus class if the bean factory was about to create a rejected/mocked class.