Mocking a service that utilizes a static initializer - java

Given the following classes, I would like to test the first one. Where I'm running into trouble is that because these are used in AWS Lambda, services are architected a certain way for performance reasons (e.g. container reuse) and I'm finding it impossible to even get an empty test class to run thanks to the static initializer.
public class ClassToTest {
private static ServiceA serviceA = new ServiceA();
public boolean methodToTest() {
return serviceA.someMethod();
}
}
public class ServiceA {
public ServiceA() {
this(new ServiceB());
}
public ServiceA(ServiceB serviceB) {
this.serviceB = serviceB;
this.someVariable = serviceB.getExpensiveServiceData();
}
}
public class ServiceB{
private static final ExpensiveService expensiveService;
private static final Map<String, Object> expensiveServiceData;
static {
expensiveService = new ExpensiveService();
expensiveServiceData = expensiveService.getData();
}
public getExpensiveServiceData() {
return expensiveServiceData.get("...");
}
}
We use Mockito for our tests. I have added PowerMockito and set up my test as follows:
#RunWith(PowerMockRunner.class)
#SuppressStaticInitializationFor({"com.x.ServiceA", "com.x.ServiceB"})
#PrepareForTest({ServiceA.class, ServiceB.class})
public class ClassToTestTest {
#InjectMocks private ClassToTest classToTest;
#Mock // All the classes...
public void methodToTestTest() {
// Doesn't even get here
classToTest.methodToTest();
}
}
This fails due to a NPE in getExpensiveServiceData() because I'm ignoring the static initializer and thus the Map never gets created. And I can't use Whitebox.setInternal to set the value programmatically because the class fails initialization before I have access to that. I assume ServiceA shouldn't actually be calling the real implementation of ServiceB but I don't know how to inject a mock into a mock like that in my test.

Related

PowerMockito null pointer when trying to use ApplicationContext

I have a class name ServiceLocator
public class ServiceLocator implements ApplicationContextAware {
private transient ApplicationContext _applicationContext;
private static ServiceLocator _instance = new ServiceLocator();
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
_instance._applicationContext = applicationContext;
}
public static ApplicationContext getApplicationContext() {
return _instance._applicationContext;
}
public static Object findService(String serviceName) {
return _instance._applicationContext.getBean(serviceName);
}
}
I am trying to use that class to find Service into Approver class methods
public class ApproverService extends AbstractDataService implements IApproverService {
public void updateCompletedInboxStatus(String status) {
IInboxService inboxService = (IInboxService)ServiceLocator.findService("inboxService");
InboxItem inboxItem = inboxService.getInboxItem("test");
inboxItem.setWorkItemStatus(status);
inboxService.saveInboxItem(inboxItem);
}
}
With that code i am trying to write Junit with PowerMockRunner
#RunWith(PowerMockRunner.class)
#PrepareForTest({ApproverService.class})
public class ApproverServiceTest {
#InjectMocks
ApproverService approverService;
#Mock
IInboxService inboxService;
#Mock
ServiceLocator serviceLocator;
#Before
public void setUp() {
MockitoAnnotations.initMocks(this);
}
#Test
public void updateCompletedInboxStatus() {
RequestAccessHeader reqHdr = new RequestAccessHeader();
reqHdr.setRequestStatus(AccessConstants.REQ_STATUS_HOLD_INT);
String status = "test";
PowerMockito.mockStatic(ServiceLocator.class);
when(serviceLocator.findService("inboxService")).thenReturn(inboxService);
approverService.updateCompletedInboxStatus(status);
}
}
But I am getting null pointer
java.lang.NullPointerException
at com.alnt.fabric.common.ServiceLocator.findService(ServiceLocator.java:25)
at com.alnt.access.approver.service.ApproverServiceTest.updateCompletedInboxStatus(ApproverServiceTest.java:80)
Please help me to find the solution for that issue.
The static method is obviously not mocked.
The problem is most probably because you haven't add the to-be-mocked class in #PrepareForTest
Change it to #PrepareForTest({ApproverService.class, ServiceLocator.class})
Off-topics:
Although it compiles, calling static method by instance reference is not a good practice. Therefore the line should be when(ServiceLocator.findService(...)).thenReturn(inboxService).
Another problem is, you tried to use Singleton pattern but in wrong way. A singleton is suppose to return you an instance so the caller can call its instance method. Your findService is preferably an instance method and to be called as ServiceLocator.getInstance().findService(...). To further improve, unless you really need it to be a singleton, you should make it a normal object instance and inject to objects that need it (given you are already using Spring, I see no reason making a Singleton)
The setup for the static method is not mocked correctly
#RunWith(PowerMockRunner.class)
#PrepareForTest({ServiceLocator.class}) //Prepare static class for mock
public class ApproverServiceTest {
#Mock
IInboxService inboxService;
#Mock
InboxItem item;
#InjectMocks
ApproverService approverService;
#Before
public void setUp() {
MockitoAnnotations.initMocks(this);
}
#Test
public void updateCompletedInboxStatus() {
//Arrange
String status = "test";
PowerMockito.mockStatic(ServiceLocator.class);
when(ServiceLocator.findService("inboxService")) //<-- NOTE static call
.thenReturn(inboxService);
when(inboxService.getInboxItem("test")).thenReturn(item);
//Act
approverService.updateCompletedInboxStatus(status);
//...
}
}
Reference Mocking Static Method
The subject under test should actually be refactored to avoid the service locator anit-pattern / code smell and should follow explicit dependency principle via constructor injection.
public class ApproverService extends AbstractDataService implements IApproverService {
private IInboxService inboxService;
#Autowired
public ApproverService(IInboxService inboxService){
this.inboxService = inboxService;
}
public void updateCompletedInboxStatus(String status) {
InboxItem inboxItem = inboxService.getInboxItem("test");
inboxItem.setWorkItemStatus(status);
inboxService.saveInboxItem(inboxItem);
}
}
That way the subject class is genuine about what it needs to perform its function correctly,
And the test can then be refactored accordingly
#RunWith(PowerMockRunner.class)
public class ApproverServiceTest {
#Mock
IInboxService inboxService;
#Mock
InboxItem item;
#InjectMocks
ApproverService approverService;
#Before
public void setUp() {
MockitoAnnotations.initMocks(this);
}
#Test
public void updateCompletedInboxStatus() {
//Arrange
String status = "test";
when(inboxService.getInboxItem("test")).thenReturn(item);
//Act
approverService.updateCompletedInboxStatus(status);
//...
}
}

Mockito when isn't replacing original method behaviour

I got 2 modules User and Email, both of them have 1 entry point which is a facade, rest is package scoped. The configuration is done in 2 classes
#Configuration
class UserConfiguration {
#Bean
UserFacade userFacade(UserRepository repository, EmailFacade emailFacade) {
return new UserFacade(repository, emailFacade);
}
}
#Configuration
class EmailConfiguration {
#Bean
EmailFacade emailFacade(EmailSender emailSender) {
return new EmailFacade(emailSender);
}
}
Now, I want to write tests that don't require Spring to start. I implemented a simple InMemoryRepository to make this happen
#RunWith(MockitoJUnitRunner.class)
public class RegisterUserTest {
#Mock
private EmailFacade emailFacade = new EmailFacade(new FakeEmailSender());
#InjectMocks
private UserFacade userFacade = new UserConfiguration().userFacade(new InMemoryUserRepository(), emailFacade);
#Before
public void setUp() {
MockitoAnnotations.initMocks(this);
}
}
I need some fake objects to instantiate EmailFacade so I wrote fake implementation
public class FakeEmailSender implements EmailSender {
#Override
public void sendEmail(EmailMessage emailMessage) throws RuntimeException {
}
}
In that scenario, I'm testing User domain, so I want to mock Email anyways.
I wrote a test to check if it works
#Test
public void shouldReturnSendingFailed() {
Mockito.when(emailFacade.sendUserVerificationEmail(Mockito.any())).thenReturn(Either.left(EmailError.SENDING_FAILED));
assertThat(userFacade.registerNewUser(RegisterUserDto.builder()
.username(USERNAME_4)
.email(VALID_EMAIL)
.password(VALID_PASSWORD).build()).getLeft(), is(EmailError.SENDING_FAILED));
}
But it isn't... after running this test I got
java.util.NoSuchElementException: getLeft() on Right
edit#
regiserNewUser() method
Either<DomainError, SuccessMessage> register(RegisterUserDto registerUserDto) {
if(userRepository.findUser(registerUserDto.getUsername()).isPresent())
return Either.left(UserError.USERNAME_ALREADY_EXISTS);
var userCreationResult = User.createUser(registerUserDto);
var savedUser = userCreationResult.map(this::saveUser);
var emailDto = savedUser.map(this::createVerificationEmail);
return emailDto.isRight() ? emailFacade.sendUserVerificationEmail(emailDto.get())
: Either.left(emailDto.getLeft());
}
Edit2#
With following test configuration
#RunWith(MockitoJUnitRunner.class)
public class RegisterUserTest {
#Mock
private EmailFacade emailFacade;
#InjectMocks
private UserFacade userFacade = new UserConfiguration().userFacade(new InMemoryUserRepository(), emailFacade);
#Before
public void setUp() {
MockitoAnnotations.initMocks(this);
}
}
I got nullpointer here, last line of registerNewUser().
Try running this code
#RunWith(MockitoJUnitRunner.class)
public class RegisterUserTest {
#Mock
private EmailFacade emailFacade;
private UserFacade userFacade;
#Before
public void setUp() {
userFacade = new UserConfiguration().userFacade(new InMemoryUserRepository(), emailFacade);
}
}
There are a few issues with your code:
You initialize your mocks twice. You don’t need to call initMocks in the setUp method if you are using Mockito runner
You are trying to inject mocks to already initialized object. But the field you are trying to inject is also passed to the constructor. Please read #InjectMocks doc, to check the strategies used to inject the mocks:
constructor (not used here, already initialized object)
setter (do you have one?)
field (is it not final)
There are details to each strategy (see my questions above). If no staregy is matched, Mockito will fail silently. The fact that you are passing an object in constructor, and rely on setter or field injection afterwards makes this code unnecesarily complex.

Spring bean scope for "one object per test method"

I have a test utility for with I need to have a fresh instance per test method (to prevent that state leaks between tests). So far, I was using the scope "prototype", but now I want to be able to wire the utility into another test utility, and the wired instances shall be the same per test.
This appears to be a standard problem, so I was wondering if there is a "test method" scope or something similar?
This is the structure of the test class and test utilities:
#RunWith(SpringRunner.class)
#SpringBootTest
public class MyTest {
#Autowired
private TestDriver driver;
#Autowired
private TestStateProvider state;
// ... state
// ... methods
}
#Component
#Scope("prototype") // not right because MyTest and TestStateProvider get separate instances
public class TestDriver {
// ...
}
#Component
public class TestStateProvider {
#Autowired
private TestDriver driver;
// ...
}
I'm aware that I could use #Scope("singleton") and #DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD) but this refreshes more than I need – a new TestDriver instance for each test would be enough. Also, this approach is error-prone because all tests using the TestDriver would need to know that they also need the #DirtiesContext annotation. So I'm looking for a better solution.
It is actually pretty easy to implement a testMethod scope:
public class TestMethodScope implements Scope {
public static final String NAME = "testMethod";
private Map<String, Object> scopedObjects = new HashMap<>();
private Map<String, Runnable> destructionCallbacks = new HashMap<>();
#Override
public Object get(String name, ObjectFactory<?> objectFactory) {
if (!scopedObjects.containsKey(name)) {
scopedObjects.put(name, objectFactory.getObject());
}
return scopedObjects.get(name);
}
#Override
public void registerDestructionCallback(String name, Runnable callback) {
destructionCallbacks.put(name, callback);
}
#Override
public Object remove(String name) {
throw new UnsupportedOperationException();
}
#Override
public String getConversationId() {
return null;
}
#Override
public Object resolveContextualObject(String key) {
return null;
}
public static class TestExecutionListener implements org.springframework.test.context.TestExecutionListener {
#Override
public void afterTestMethod(TestContext testContext) throws Exception {
ConfigurableApplicationContext applicationContext = (ConfigurableApplicationContext) testContext
.getApplicationContext();
TestMethodScope scope = (TestMethodScope) applicationContext.getBeanFactory().getRegisteredScope(NAME);
scope.destructionCallbacks.values().forEach(callback -> callback.run());
scope.destructionCallbacks.clear();
scope.scopedObjects.clear();
}
}
#Component
public static class ScopeRegistration implements BeanFactoryPostProcessor {
#Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory factory) throws BeansException {
factory.registerScope(NAME, new TestMethodScope());
}
}
}
Just register the test execution listener, and there will be one instance per test of all #Scope("testMethod") annotated types:
#RunWith(SpringRunner.class)
#SpringBootTest
#TestExecutionListeners(listeners = TestMethodScope.TestExecutionListener.class,
mergeMode = MergeMode.MERGE_WITH_DEFAULTS)
public class MyTest {
#Autowired
// ... types annotated with #Scope("testMethod")
}
I ran into the same problem some time ago and came to this solution:
Use Mocks
I wrote some methods to create specific mockito settings to add behavior to each mock.
So create a TestConfiguration class with following methods and bean definition.
private MockSettings createResetAfterMockSettings() {
return MockReset.withSettings(MockReset.AFTER);
}
private <T> T mockClass(Class<T> classToMock) {
return mock(classToMock, createResetAfterMockSettings());
}
and your bean definition will look like:
#Bean
public TestDriver testDriver() {
return mockClass(TestDriver .class);
}
MockReset.AFTER is used to reset the mock after the test method is run.
And finally add a TestExecutionListeners to your Test class:
#TestExecutionListeners({ResetMocksTestExecutionListener.class})

Using #InjectMocks to replace #Autowired field with a mocked implementation

I want to replace an autowired class of a service in my spring boot app with a mocked implementation of that class that I created specifically for testing.
I chose to create this mocked implementation because the behaviour of this class is too complicated to mock using mockito as it requires multiple other mocks itself.
I am not able to work out how to inject this mocked implementation into the service.
Here is a minimal example of the situation:
#Service
public class ServiceIWantToTestImpl implements ServiceIWantToTest{
#Autowired
ComplicatedDependency complicatedDependency;
#Override
public void methodUsingDependency(){
String string = complicatedDependency.doSomething();
System.out.println(string);
}
}
public class MockComplicatedDependency implements ComplicatedDepencency{
public MockComplicatedDependency(...){
// Inject other mocked objects into this mock
}
public String doSomthing(){
// This would be a mocked version of this function for testing
return "test";
}
}
#RunWith(MockitoJUnitRunner.class)
public class TestingTheService(){
#InjectMock
private static ServiceIWantToTest serviceIWantToTest = new ServiceIWantToTestImpl();
#Mock
ComplicatedDependency mockComplicatedDependency;
#BeforeClass
public static void init(){
mockComplicatedDependency = new MockComplicatedDependency(...);
}
#Test
public void testAttempt(){
serviceIWantToTest.methodUsingDependency(); // This method calls complicatedDependency.doSomething() which does not run the mocked version in MockComplicatedDependency which I wanted to inject, and would always return null instead of the "test" string I put in this example.
}
}
Do you have to use Mockito annotations to setup dependencies for the class under test?
If that is not the main constraint why not just do the plain simple setup and introduce a constructor or a setter in ServiceIWantToTestImpl class for the ComplicatedDependency field and set the dependency in your test setup directly to whatever impl of ComplicatedDependency you like e.g.:
#Service
public class ServiceIWantToTestImpl implements ServiceIWantToTest {
#Autowired
ComplicatedDependency complicatedDependency;
public ServiceIWantToTestImpl() {
}
public ServiceIWantToTestImpl(ComplicatedDependency complicatedDependency) {
this.complicatedDependency = complicatedDependency;
}
#Override
public void methodUsingDependency(){
String string = complicatedDependency.doSomething();
System.out.println(string);
}
}
public class TestingTheService {
private static ServiceIWantToTestImpl serviceIWantToTest;
#BeforeClass
public static void init(){
serviceIWantToTest = new ServiceIWantToTestImpl(new MockComplicatedDependency());
}
#Test
public void testAttempt() {
serviceIWantToTest.methodUsingDependency();
}
}
That is one way.
To make it work with Mockito, You could to use #Spy instead of #Mock like this:
#RunWith(MockitoJUnitRunner.class)
public class TestingTheService {
#InjectMocks
private static ServiceIWantToTestImpl serviceIWantToTest = new ServiceIWantToTestImpl();
#Spy
private static ComplicatedDependency complicatedDependency = new MockComplicatedDependency();
#BeforeClass
public static void init() {
}
#Test
public void testAttempt() {
serviceIWantToTest.methodUsingDependency();
}
}
Though this is a bit of a hack. I strongly recommend that you read the JavaDoc of the #Spy annotation and make sure it's expected use is what you really need for your test.

JMockit : How to inject a MockUp class into a tested class?

I'm new to JMockIt and am trying to figure out/understand how to use #Injectable for a top-level MockUp class that I have already defined.
For example:
// JUnit Test Class
public class RepositoryTest {
#Tested private Repository repository;
#Injectable private ResultsAPIWrapper resultsApiWrapper;
#Test
public void testRepo(){
new ResultsApiWrapper();
assertThat(repository.doSomething(), is("done" ) );
}
}
// Class under test
public class Repository{
#Autowired private ResultsAPIWrapper resultsApiWrapper;
public String doSomething(){
return resultsApiWrapper.load();
}
}
// Mocked implementation of the ResultsAPIWrapper that I want injected into Repository
public class ResultsApiWrapperMock extends MockUp<ResultsAPIWrapper>{
#Mock
public String load(){
return "done";
}
}
If I try the above, I get an error
java.lang.IllegalArgumentException: Class already mocked: com.fw.wrappers.ResultsAPIWrapper
at com.fw.wrappers.mock.ResultsApiWrapperMock.<init>(ResultsApiWrapperMock.java:12)
at com.fw.repository.RepositoryTest.testRepo(RepositoryTest.java:38)
But If I remove the new ResultsApiWrapper() then I do not know how to specify which is the class I want to use as my mocked implementation for the autowire.
Am I misunderstanding how to do this? How can I specify that I want JMockit to autowire using my MockUp<> implementation?
I'm new too but I think something like this would work in your case...
This will mock the ResultsAPIWrapper() and not initialize any static variables and create a specific mock for load():
new MockUp<ResultsAPIWrapper>() {
#Mock
void $clinit() {
//disable static initialization
}
#Mock
public String load() {
return "done";
}
};

Categories

Resources