Mockito Mock a static void method with Mockito.mockStatic() - java

I'm using Spring Boot and in one of my unit test, I need to mock the Files.delete(somePath) function. Which is a static void method.
I know that with Mockito it is possible to mock void method:
doNothing().when(MyClass.class).myVoidMethod()
And since July 10th 2020, it is possible to mock static method:
try (MockedStatic<MyStaticClass> mockedStaticClass = Mockito.mockStatic(MyStaticClass.class)) {
mockedStaticClass.when(MyStaticClass::giveMeANumber).thenReturn(1L);
assertThat(MyStaticClass.giveMeANumber()).isEqualTo(1L);
}
But I can't manage to mock a static void mehtod such as Files.delete(somePath).
This is my pom.xml file (only test related dependencies):
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>
<version>3.5.15</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>3.5.15</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<version>3.5.15</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<version>2.2.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
Is there a way to mock static void methods without using PowerMockito ?
If it is possible, what is the correct syntax to do so ?

In general mocking static calls is the last resort, that is not supposed to be used as default approach.
For example, for testing the code, that works with file system, there are better means. E.g. depending on the junit version either use TemporaryFolder rule or #TempDir annotation.
Also, please note, that Mockito.mockStatic might significantly slow down your tests (e.g. look at the notes below).
Having said the caution above, find the snippet below, that shows how to test, that file got removed.
class FileRemover {
public static void deleteFile(Path filePath) throws IOException {
Files.delete(filePath);
}
}
class FileRemoverTest {
#TempDir
Path directory;
#Test
void fileIsRemovedWithTemporaryDirectory() throws IOException {
Path fileToDelete = directory.resolve("fileToDelete");
Files.createFile(fileToDelete);
FileRemover.deleteFile(fileToDelete);
assertFalse(Files.exists(fileToDelete));
}
#Test
void fileIsRemovedWithMockStatic() throws IOException {
Path fileToDelete = Paths.get("fileToDelete");
try (MockedStatic<Files> removerMock = Mockito.mockStatic(Files.class)) {
removerMock.when(() -> Files.delete(fileToDelete)).thenAnswer((Answer<Void>) invocation -> null);
// alternatively
// removerMock.when(() -> Files.delete(fileToDelete)).thenAnswer(Answers.RETURNS_DEFAULTS);
FileRemover.deleteFile(fileToDelete);
removerMock.verify(() -> Files.delete(fileToDelete));
}
}
}
Notes:
Mockito.mockStatic is available in Mockito 3.4 and above, so check you're using correct version.
The snippet deliberatly shows two approaches: #TempDir and Mockito.mockStatic. When run both tests you'll notice that Mockito.mockStatic is much slower. E.g. on my system test with Mockito.mockStatic runs around 900 msec vs 10 msec for #TempDir.

Related

class junit.framework.TestSuite cannot be cast to class org.junit.jupiter.api.Test

I am new to JAVA and Junit and trying to do something simple. I have the test passed but I see in the terminal initailizationError side this error "class junit.framework.TestSuite cannot be cast to class org.junit.jupiter.api.Test" This is the version of my Junit dependencies.
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.9.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.9.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>RELEASE</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
This is the test that I am trying to run
import junit.framework.TestCase;
import junit.framework.TestSuite;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Assertions;
/**
* Unit test for simple App.
*/
public class AppTest
extends TestCase
{
#Test
public void firstTest() {
Assertions.assertEquals(2, 2);
}
/**
* Create the test case
*
* #param testName name of the test case
*/
/**
* #return the suite of tests being tested
*/
public static Test suite()
{
return (Test) new TestSuite( AppTest.class );
}
/**
* Rigourous Test :-)
*/
public void testApp()
{
assertTrue( true );
}
}
I could not understand the error as it is my first time useing it
I think the simplest fix is for you to delete your suite method. You don't need it, and your tests will run quite happily without it. Once you are a bit more confident and familiar with Java and JUnit, then maybe suites will help you organise and group tests, but you can certainly start without them.
The way you are attempting to create a test suite seems to follow the approach of JUnit 3, but you are using JUnit 5. JUnit changed a lot from JUnit 3 and JUnit 4, and also from JUnit 4 to JUnit 5, so it's not surprising that something from JUnit 3 doesn't work with JUnit 5.
I hadn't seen this way of writing test suites before, but I did find that this page talks about JUnit 5 and then presents examples using JUnit 3. To be quite frank I found the content of that page to be of poor quality and cannot recommend it. If you are using that page to learn about JUnit then I would advise you to look elsewhere.
the issue comes from these lines :
public static Test suite()
{
return (Test) new TestSuite( AppTest.class );
}
you are trying to cast 2 different Classes :
class junit.framework.TestSuite
to
class org.junit.jupiter.api.Test
which are 2 different classes , you can simply remove this function and the error will be fixed.
or if you want to have a TestSuite to bundle a few unit test cases and run them together,it can be in a separate class.
like what described in this article
hope this helps !!!

Guava Google using Reflections in java

I am trying to use Reflections in my Java project (also using Spring boot), and I have to get all the classes of a package that implements an interface.
public static Object[] getClasses(String packageName) throws IOException
{
Reflections r = new Reflections(packageName);
Set<Class<? extends EntityXML>> allClasses = r.getSubTypesOf(EntityXML.class);
return (Object[]) allClasses.toArray();
}
But it returns me always this error.
java.lang.NoSuchMethodError: com.google.common.collect.Sets$SetView.iterator()Lcom/google/common/collect/UnmodifiableIterator;
at org.reflections.Reflections.expandSuperTypes(Reflections.java:380)
at org.reflections.Reflections.<init>(Reflections.java:126)
at org.reflections.Reflections.<init>(Reflections.java:168)
at org.reflections.Reflections.<init>(Reflections.java:141)
at gcs.fds.focus.lib.utils.FindPackages.getClasses(FindPackages.java:14)
...
I try to import the google.guava package to avoid this error, and it fix it, but I would like not to import it, because it is a package without use in my project.
Any idea about how to avoid this error?
Okey I solved!
I deleted the google.guava dependency, and I make a
mvn dependency:tree
where I could see that other of my dependencies imports also google.guava, but version 19 which I read that makes this conflict. (everit.json dependency), so I exclude the google.guava from this dependency and it works!
<dependency>
<groupId>org.everit.json</groupId>
<artifactId>org.everit.json.schema</artifactId>
<version>1.3.0</version>
<exclusions>
<exclusion>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</exclusion>
</exclusions>
</dependency>

JUnit change class or method behaviour in integration tests

I have a java application (no Spring inside) that I want to test with an integration test.
My main use case is the main function that with a specified input do some things on the database and send some request to two different services, one SOAP and one REST.
Now I have a working JUnit configuration (splitted in unit and integration tests) + io.fabric8:docker-maven-plugin that use a docker image for the database during integration tests.
What I'm trying to do is to add a mock for these 2 services, in particular, the method that is used to call directly the external service.
The big problem is that I have this structure:
class A{
Result mainFunction(Request r){
....
B b = new B(params);
b.logEvent(someParameters)
....
}
}
class B{
int logEvent(Object someParameters){
....
NotifierHandler nh = new NotifierHandler(param1);
nh.sendNotification(json);
....
}
}
where I have:
class NotifierHandler{
String sendNotification(Json j){
...
[call to REST service with some parameters]
...
...
[call to SOAP service with some parameters]
...
}
}
What I need: call A.mainFunction(r) having, in the test environment, replaced the NotifierHandler with a FakeNotifierHandler and/or change the behaviour of the method sendNotification().
Actual problems: Using Mockito and PowerMock now I have the problem that I'm not able to change globally and directly the class NotifierHandler with FakeNotifierHandler. The same trying to changing the behaviour of the method.
In particular, what I need is to create a
class FakeNotifierHandler{
String sendNotification(Json j){
...
[save on an HashMap what I should send to the REST service]
...
...
[save on another HashMap what I should send to the SOAP service]
...
}
}
Reading all example that I tryed I saw only simple examples that change the return value of a method and not the behaviour of one method of one class used by another and another that I'm using as the start point of the integration test.
NOTE: probably there is a fast way to do this but I'm very new on this type of tests (Mockito, PowerMock,...) and I have found no example for this particular strange case.
EDIT: not similar to How to mock constructor with PowerMockito because I need to change the behaviour of the method, not only the return value.
Thanks a lot in advance
I found a solution that works very well and it is very simple!
The solution is PowerMock (https://github.com/powermock/powermock) and in particular replace the creation of an instance of a class with another: https://github.com/powermock/powermock/wiki/mockito#how-to-mock-construction-of-new-objects
There is only one problem in my project and it is JUnit 5. PowerMock support JUnit 4 and for this reason, only for some tests of the solution are using it.
In order to do this there is the needed to replace
import org.junit.jupiter.api.Test;
with
import org.junit.Test;
In order to use teh "whenNew()" methods I had extented the class that in tests must be replaced and I have overwritten only methods that are necessary for the integration test.
The big benefit of this solution is that my code is untouched and I can use this approach also on old code without the risk of introducing regressions during the refactor of the code.
Regarding the code of a integration test, here an example:
import org.junit.jupiter.api.DisplayName;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PowerMockIgnore;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
#RunWith(PowerMockRunner.class)
#PowerMockIgnore({"javax.crypto.*" }) // https://github.com/powermock/powermock/issues/294
#PrepareForTest(LegacyCoreNetworkClassPlg.class) // it is the class that contains the "new SOAPCallHelper(..)" code that I want to intercept and replace with a stub
public class ITestExample extends InitTestSuite {
#Test
#DisplayName("Test the update of a document status")
public void iTestStubLegacyNetworkCall() throws Exception {
// I'm using JUnit 4
// I need to call #BeforeAll defined in InitTestSuite.init();
// that works only with JUnit 5
init();
LOG.debug("IN stubbing...");
SOAPCallHelperStub stub = new SOAPCallHelperStub("empty");
PowerMockito.whenNew(SOAPCallHelper.class).withAnyArguments().thenReturn(stub);
LOG.debug("OUT stubbing!!!");
LOG.debug("IN iTestStubLegacyNetworkCall");
...
// Here I can create any instance of every class, but when an instance of
// LegacyCoreNetworkClassPlg.class is created directly or indirectly, PowerMock
// is checking it and when LegacyCoreNetworkClassPlg.class will create a new
// instance of SOAPCallHelper it will change it with the
// SOAPCallHelperStub instance.
...
LOG.debug("OUT iTestStubLegacyNetworkCall");
}
}
Here the configuration of the pom.xml
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<junit.jupiter.version>5.5.2</junit.jupiter.version>
<junit.vintage.version>5.5.2</junit.vintage.version>
<junit.platform.version>1.3.2</junit.platform.version>
<junit.platform.engine.version>1.5.2</junit.platform.engine.version>
<powermock.version>2.0.2</powermock.version>
<!-- FOR TEST -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>${junit.jupiter.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<!-- Only required to run tests in an IDE that bundles an older version -->
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-launcher</artifactId>
<version>${junit.platform.version}</version>
<scope>test</scope>
</dependency>
<!-- Only required to run tests in an IDE that bundles an older version -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>${junit.jupiter.version}</version>
<scope>test</scope>
</dependency>
<!-- Only required to run tests in an IDE that bundles an older version -->
<dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
<version>${junit.vintage.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-engine</artifactId>
<version>${junit.platform.engine.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<version>${junit.vintage.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>${powermock.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito2</artifactId>
<version>${powermock.version}</version>
<scope>test</scope>
</dependency>
I think the main headache in your case is that you have tightly coupled dependencies between class A, B and NotifierHandler. I would start with:
class A {
private B b;
public A(B b) {
this.b = b;
}
Result mainFunction(Request r){
....
b.logEvent(someParameters)
....
}
}
class B {
private NotifierHandler nh;
public B(NotifierHandler nh) {
this.nh = nh;
}
int logEvent(Object someParameters){
....
nh.sendNotification(json);
....
}
}
Make NotifierHanlder an interface:
interface NotifierHandler {
String sendNotification(String json);
}
and make two implementations: one for a real use case, and one fake that you can stub whatever you want:
class FakeNotifierHandler implements NotifierHandler {
#Override
public String sendNotification(String json) {
// whatever is needed for you
}
}
Inject FakeNotifierHandler in your test.
I hope this helps you.

Powermockito whenNew returns null if not matched

I do not know if it supposed to do that, but I guess not. Have a look to my code below.
File mocked = PowerMockito.mock(File.class);
PowerMockito.whenNew(File.class).withParameterTypes(String.class).withArguments(eq(THE_TARGET_PATH)).thenReturn(mocked);
File normalFile = new File(WORKING_PATH);
File mockedFile = new File(THE_TARGET_PATH);
I do expect that the normalFile will be created normally, but it is actually null. The mockedFile is mocked correctly btw.
I am also using #RunWith(PowerMockRunner.class) and #PrepareForTest({ClassWhereInstanceIsCreated.class, File.class})
and I am using:
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>1.7.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito</artifactId>
<version>1.7.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-core</artifactId>
<version>1.7.4</version>
<scope>test</scope>
</dependency>
My finding shows that there is no good way to do partial constructor mocking with PowerMockito / PowerMockito 2. By the logic you should be able to do something like
PowerMockito.whenNew(File.class).withParameterTypes(String.class)
.withArguments(eq(WORKING_PATH)).thenCallRealMethod();
But this will trigger internal exception in the PowerMockito similar to this
org.mockito.exceptions.base.MockitoException: Cannot call abstract real method on java object! Calling real methods is only possible when mocking non abstract method. //correct example: when(mockOfConcreteClass.nonAbstractMethod()).thenCallRealMethod();
Thus, the only way I can see is to re-write the test. You should construct all your required File objects first, before mocking constructor and give PowerMockito rules which to return in each particualr case.
File mocked = Mockito.mock(File.class);
// create file as you want
File realFile = new File(WORKING_PATH);
// tell PowerMockito to return it
PowerMockito.whenNew(File.class).withParameterTypes(String.class)
.withArguments(Mockito.eq(WORKING_PATH)).thenReturn(realFile);
// tell PowerMockito to return mock if other argument passed
PowerMockito.whenNew(File.class).withParameterTypes(String.class)
.withArguments(Mockito.eq(THE_TARGET_PATH)).thenReturn(mocked);
File normalFile = new File(WORKING_PATH);
File mockedFile = new File(THE_TARGET_PATH);
This is undesirable solution, but I could not offer anything better.
Hope it helps!

Cucumber on IntelliJ: getting errors as soon as I set up new project

I've been stuck for hours and I'm a bit confused, as I've tried to follow quite a few tutorials to set up Cucumber (Java version) + Selenium using IntelliJ as an IDE but I always end up getting errors from the very beginning, so I'm guessing there either is something the tutorials don't mention or some misconfiguration on my IDE.
This is something of what I've tried:
First, created a Maven project in IntelliJ IDEA and added dependencies for cucumber, junit and selenium to my pom.xml:
<!-- https://mvnrepository.com/artifact/info.cukes/cucumber-java -->
<dependency>
<groupId>info.cukes</groupId>
<artifactId>cucumber-java</artifactId>
<version>1.2.5</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.seleniumhq.selenium/selenium-java -->
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<version>3.8.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/junit/junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/info.cukes/cucumber-junit -->
<dependency>
<groupId>info.cukes</groupId>
<artifactId>cucumber-junit</artifactId>
<version>1.2.5</version>
<scope>test</scope>
</dependency>
Then I created some structure for my project:
a folder named "resources" within src/test.
a folder named "features" within src/test/resources.
a package named "step_definitions" within src/test/java.
a file called MyTest.feature in the src/test/resources/features folder
In my MyTest.feature I added a simple test like this:
Feature: Check addition in Google calculator
Scenario: Addition
Given I open google
When I enter "2+2" in search textbox
Then I should get the result as "4"
Then from my feature file I auto-generated step definitions by using the IDE functionality (alt+enter > create step definitions), and I got a new file: MyStepDefs.java which I placed in src/test/java/step_definitions (leaving it in just src/test/java makes no difference), with the following contents:
package step_definitions;
import cucumber.api.PendingException;
public class MyStepdefs {
public MyStepdefs() {
Given("^I open google$", () -> {
// Write code here that turns the phrase above into concrete actions
throw new PendingException();
});}
}
The thing is, this is already showing errors. The "Given" keyword is not recognized: Cannot resolve method 'Given(java.lang.String)'
And on "new PendingException()" I get: Incompatible types. Required: java.lang.Throwable. Found: cucumber.api.PendingException
This sounds fishy, as it's auto-generated code so I assume it should be error-free (but it's not).
So I tried replacing this auto-generated code with something I got from this tutorial but then I get a "not applicable to method" error on #Before, #After, #Given, #When, #Then keywords.
package step_definitions;
import org.junit.Assert;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.By;
import cucumber.api.java.After;
import cucumber.api.java.Before;
import cucumber.api.java.en.Given;
import cucumber.api.java.en.Then;
import cucumber.api.java.en.When;
public class googleCalcStepDefinition {
protected WebDriver driver;
#Before
public void setup() {
driver = new FirefoxDriver();
}
#Given("^I open google$")
public void I_open_google() {
//Set implicit wait of 10 seconds and launch google
driver.manage().timeouts().implicitlyWait(5, TimeUnit.SECONDS);
driver.get("https://www.google.co.in");
}
#When("^I enter \"([^\"]*)\" in search textbox$")
public void I_enter_in_search_textbox(String additionTerms) {
//Write term in google textbox
WebElement googleTextBox = driver.findElement(By.id("gbqfq"));
googleTextBox.sendKeys(additionTerms);
//Click on searchButton
WebElement searchButton = driver.findElement(By.id("gbqfb"));
searchButton.click();
}
#Then("^I should get result as \"([^\"]*)\"$")
public void I_should_get_correct_result(String expectedResult) {
//Get result from calculator
WebElement calculatorTextBox = driver.findElement(By.id("cwos"));
String result = calculatorTextBox.getText();
//Verify that result of 2+2 is 4
Assert.assertEquals(result, expectedResult);
driver.close();
}
#After
public void closeBrowser() {
driver.quit();
}
}
What am I missing? Is there any way I can set up a fresh new project that uses Cucumber (Java) + Selenium on the IntelliJ IDE? Or is it just not possible at all?
Thanks!
Apparently, the Java JDK 9 that I had recently downloaded was the culprit. I went back to square 1 and started the project with JDK 8 and everything works as expected now.

Categories