How to verify UnnecessaryStubbingException automatically for mockbean? - spring

I am using the MockitoExtension. It verify whether i have UnnecessaryStubbing.
#ExtendWith(MockitoExtension.class)
public class MyTest1 {
#Mock
TargetWithMock targetWithMock;
#Test
public void targetWithMock() {
doNothing().when(targetWithMock).test();
// throw org.mockito.exceptions.misusing.UnnecessaryStubbingException:Unnecessary stubbings
// detected.
}
}
class TargetWithMock {
void test() {
}
}
I am using the mockbean with MockitoExtension. but It doesn't throw UnnecessaryStubbing.
#ExtendWith(MockitoExtension.class)
#SpringBootTest
public class MyTest2 {
#Mock
Target targetWithMock;
#MockBean
Target targetWithMockBean;
#Test
public void targetWithMock() {
doNothing().when(targetWithMock).test();
// throw org.mockito.exceptions.misusing.UnnecessaryStubbingException:Unnecessary stubbings
// detected.
}
#Test
public void targetWithMockBean() {
doNothing().when(targetWithMockBean).test();
// I want to throw org.mockito.exceptions.misusing.UnnecessaryStubbingException, but not occur
}
}
#Service
class Target {
void test() {
}
}
I know how to verfiy but i want to verify UnnecessaryStubbing automatically.
How to verify UnnecessaryStubbing automatically?
verify(someClass).someMethod();
I want to like this
#ExtendWith(MockBeanExtension.class) or #ExtendWith(MockitoExtension.class) + #MockBeanSetting

Related

How to test a try...finally method only been called once in SpringBoot?

I am following this article to implement a database read/write separation feature by calling different methods. However, I got the error:
Missing method call for verify(mock) here: verify(spyDatabaseContextHolder, times(1)).set(DatabaseEnvironment.READONLY);
when doing the testing.
My test case is trying to verify DatabaseEnvironment.READONLY has been set once when using TransactionReadonlyAspect AOP annotation:
// TransactionReadonlyAspectTest.java
#RunWith(SpringRunner.class)
#ContextConfiguration(classes = {LoadServiceImpl.class, TransactionReadonlyAspect.class})
public class TransactionReadonlyAspectTest {
#Autowired
private TransactionReadonlyAspect transactionReadonlyAspect;
#MockBean
private LoadServiceImpl loadService;
#Test
public void testReadOnlyTransaction() throws Throwable {
ProceedingJoinPoint mockProceedingJoinPoint = mock(ProceedingJoinPoint.class);
Transactional mockTransactional = mock(Transactional.class);
DatabaseContextHolder spyDatabaseContextHolder = mock(DatabaseContextHolder.class);
when(mockTransactional.readOnly()).thenReturn(true);
when(loadService.findById(16)).thenReturn(null);
when(mockProceedingJoinPoint.proceed()).thenAnswer(invocation -> loadService.findById(16));
transactionReadonlyAspect.proceed(mockProceedingJoinPoint, mockTransactional);
verify(spyDatabaseContextHolder, times(1)).set(DatabaseEnvironment.READONLY); // got the error: Missing method call for verify(mock)
verify(loadService, times(1)).findById(16);
assertEquals(DatabaseContextHolder.getEnvironment(), DatabaseEnvironment.UPDATABLE);
}
}
//TransactionReadonlyAspect.java
#Aspect
#Component
#Order(0)
#Slf4j
public class TransactionReadonlyAspect {
#Around("#annotation(transactional)")
public Object proceed(ProceedingJoinPoint proceedingJoinPoint,
org.springframework.transaction.annotation.Transactional transactional) throws Throwable {
try {
if (transactional.readOnly()) {
log.info("Inside method " + proceedingJoinPoint.getSignature());
DatabaseContextHolder.set(DatabaseEnvironment.READONLY);
}
return proceedingJoinPoint.proceed();
} finally {
DatabaseContextHolder.reset();
}
}
}
// DatabaseContextHolder.java
public class DatabaseContextHolder {
private static final ThreadLocal<DatabaseEnvironment> CONTEXT = new ThreadLocal<>();
public static void set(DatabaseEnvironment databaseEnvironment) {
CONTEXT.set(databaseEnvironment);
}
public static DatabaseEnvironment getEnvironment() {
DatabaseEnvironment context = CONTEXT.get();
System.out.println("context: " + context);
return CONTEXT.get();
}
public static void reset() {
CONTEXT.set(DatabaseEnvironment.UPDATABLE);
}
}
//DatabaseEnvironment.java
public enum DatabaseEnvironment {
UPDATABLE,READONLY
}
// LoadServiceImpl.java
#Service
public class LoadServiceImpl implements LoadService {
#Override
#Transactional(readOnly = true)
public LoadEntity findById(Integer Id) {
return this.loadDAO.findById(Id);
}
...
}
I just want to test DatabaseContextHolder.set(DatabaseEnvironment.READONLY) has been used once then in the TransactionReadonlyAspect finally block it will be reset to DatabaseEnvironment.UPDATABLE which make sense.
However, how to test DatabaseContextHolder.set(DatabaseEnvironment.READONLY) gets called once? Why does this error occur? Is there a better way to test TransactionReadonlyAspect?

Mocking a service in a Testcontainer Spring boot test

I am quite new in Spring and I am facing an issue right now with testing:
I have the following Service:
#Service
public class MyService {
public Integer getKey() {
List<Integer> keys = getKeys(1);
if (keys.size() == 1) {
return keys.get(0);
}
throw new IllegalArgumentException("Error!");
}
... and a getKeys() method, which provides a list based ona rest call...
}
And I use this service class in antother class:
#NoArgsConstructor
public class MyOtherClass extends MyClass {
#Autowired
private MyService myService;
....
#Override public KeyValue<Object, Object> doSomething(Object key, Object value) {
if (conditionIsTrue(key, value)) {
MyObject obj = new MyObject();
myObject.setKey(keyService.getKey()); ----- here is always null the keyService
.....
} else {
return KeyValue.pair(null, null);
}
}
And I try to write a test but the MyService is always null..
#ActiveProfiles("my-test")
#SpringBootTest(classes = Application.class)
#Testcontainers
#Slf4j
public class MyTest extends TestContext {
#BeforeEach
void init(final TestInfo testInfo) {
....
}
#AfterEach
void deinit() {
....
}
#Test
public void myTest() {
....
}
How can I inject a mock MyService into the test container?
Thank you!

Is there a way to get event in spring (+junit) fired after all tests classes?

I use spring 5 + junit 5. And I have two classes - BarIT and FooIT.
#ExtendWith({SpringExtension.class})
#ContextConfiguration(classes = ModuleContextConfig.class)
public class FooIT {
#Test
public void foo() {
System.out.println("Foo");
}
}
#ExtendWith({SpringExtension.class})
#ContextConfiguration(classes = ModuleContextConfig.class)
public class BarIT {
#Test
public void bar() {
System.out.println("Bar");
}
}
This is my suite:
#RunWith(JUnitPlatform.class)
#ExtendWith({SpringExtension.class})
#SelectClasses({
FooIT.class,
BarIT.class
})
#IncludeClassNamePatterns(".*IT$")
public class SuiteIT {
}
I want to get event when tests in two classes have been executed, I mean event after FooIT.foo() and BarIT.bar(), however, I can't do that. I hoped to get ContextClosedEvent but it is not fired:
#Component
#Scope(value = ConfigurableBeanFactory.SCOPE_SINGLETON)
public class ApplicationListenerBean implements ApplicationListener {
#Override
public void onApplicationEvent(ApplicationEvent event) {
System.out.println("Event:" + event.toString());
}
}
And this is the output:
Event:org.springframework.context.event.ContextRefreshedEvent..
Event:org.springframework.test.context.event.PrepareTestInstanceEvent..
Event:org.springframework.test.context.event.BeforeTestMethodEvent..
Event:org.springframework.test.context.event.BeforeTestExecutionEvent..
Foo
Event:org.springframework.test.context.event.AfterTestExecutionEvent...
Event:org.springframework.test.context.event.AfterTestMethodEvent...
Event:org.springframework.test.context.event.AfterTestClassEvent...
Event:org.springframework.test.context.event.BeforeTestClassEvent...
Event:org.springframework.test.context.event.PrepareTestInstanceEvent...
Event:org.springframework.test.context.event.BeforeTestMethodEvent...
Event:org.springframework.test.context.event.BeforeTestExecutionEvent...
Bar
Event:org.springframework.test.context.event.AfterTestExecutionEvent...
Event:org.springframework.test.context.event.AfterTestMethodEvent...
Event:org.springframework.test.context.event.AfterTestClassEvent..
Could anyone say how to do it, if it is possible.

PowerMock whenNew Problem On Spring Component Constructor

I have a Spring Service like below:
#Service
public class SendWithUsService
{
private SendWithUs mailAPI;
public SendWithUsService()
{
this.mailAPI = new SendWithUs();
}
public void sendEmailEvent(Dto data)
{
try
{
SendWithUsSendRequest request = new SendWithUsSendRequest()...;
mailAPI.send(request);
}
catch (Exception e)
{
...
}
}
}
And my test code look like below:
#RunWith(PowerMockRunner.class)
#PowerMockIgnore({"javax.net.ssl.*"})
#PrepareForTest(SendWithUsService.class)
public class SendWithUsServiceTest
{
#InjectMocks
private SendWithUsService sendWithUsService;
#Mock
private SendWithUs mailAPI;
#Test
public void sendEmailEvent_successfully() throws Exception
{
whenNew(SendWithUs.class).withAnyArguments().thenReturn(mailAPI);
Dto emailData = ...;
sendWithUsService.sendEmailEvent(emailData);
...
}
}
In here, PowerMock whenNew method doesn't work. But when I call it outside of constructor like inside the sendEmailEvent method, it is triggered.
Is there a way to handle it?
Works:
public void sendEmailEvent(Dto data)
{
this.mailAPI = new SendWithUs();
...
}
Not works:
public SendWithUsService()
{
this.mailAPI = new SendWithUs();
}
I've solved it like below:
#RunWith(PowerMockRunner.class)
#PowerMockIgnore({"javax.net.ssl.*"})
#PrepareForTest(SendWithUsService.class)
public class SendWithUsServiceTest
{
#InjectMocks
private SendWithUsService sendWithUsService;
#Mock
private SendWithUs mailAPI;
#Before
public void setUp() throws Exception {
whenNew(SendWithUs.class).withAnyArguments().thenReturn(mailAPI);
MockitoAnnotations.initMocks(this);
}
#Test
public void sendEmailEvent_successfully() throws Exception
{
Dto emailData = ...;
sendWithUsService.sendEmailEvent(emailData);
...
}
}

SpringBootTest - how to assert if context loading fails

I wrote an ApplicationListener that should check if the environment is prepared during context initialization. I'm having trouble testing the scenario since I'm adding the listener manually both in my configure() and main() methods.
ApplicationListener class:
public class EnvironmentPrepared implements ApplicationListener<ApplicationEnvironmentPreparedEvent> {
#Override
public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
//code that checks if conditions are met
if (checkTrue) {
throw new RuntimeException();
}
}
}
Main class:
public class MyApp extends SpringBootServletInitializer {
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
setRegisterErrorPageFilter(false);
return application.listeners(new EnvironmentPrepared()).sources(MyApp.class);
}
public static void main(String[] args) {
SpringApplication springApplication = new SpringApplication(MyApp.class);
springApplication.addListeners(new EnvironmentPrepared());
springApplication.run(args);
}
}
The test I want to execute:
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#ContextConfiguration(loader = OverriddenProfilesTest.CustomLoader.class)
public class OverriddenProfilesTest {
public static class CustomLoader extends SpringBootContextLoader {
#Override
protected SpringApplication getSpringApplication() {
SpringApplication app = super.getSpringApplication();
app.addListeners(new EnvironmentPrepared());
return app;
}
}
/**
* Checks if spring can bootstrap everything
*/
#Test(expected = RuntimeException.class)
public void test() {
}
}
This would be the test I want. A RuntimeException is thrown but the exception happens during context initialization so the test doesn't even start.
Here is the solution I used. I removed the manual adding of the listener to the application and used spring.factories file instead.
Regarding the test, I first created a custom runner class:
public class SpringRunnerWithExpectedExceptionRule extends SpringJUnit4ClassRunner {
public SpringRunnerWithExpectedExceptionRule(Class<?> clazz) throws InitializationError {
super(clazz);
}
#Override
protected Statement methodBlock(FrameworkMethod frameworkMethod) {
List<ExpectedException> testRules = getTestClass().getAnnotatedFieldValues(null, ExpectedExceptionClassRule.class, ExpectedException.class);
Statement result = super.methodBlock(frameworkMethod);
for (TestRule item : testRules) {
result = item.apply(result, getDescription());
}
return result;
}}
Then I create following annotation:
#Retention(RUNTIME)
#Target({ FIELD })
public #interface ExpectedExceptionClassRule {
}
And finally, I was able to run the test with my runner:
#RunWith(SpringRunnerWithExpectedExceptionRule.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class OverriddenProfilesTest {
#ExpectedExceptionClassRule
public static ExpectedException expectedException = ExpectedException.none();
#BeforeClass
public static void before() {
expectedException.expectCause(runtimeExceptionMethod());
}
#Test
public void testThatShouldThrowExceptionWhileSettingContext {
}
static Matcher<Throwable> runtimeExceptionMethod() {
return new IsRuntimeException();
}
static class IsRuntimeException extends TypeSafeMatcher<Throwable> {
//do stuff
}
More on the solution can be found here.

Resources