Mock static Methods in ScalaTests or Spec2 - java

I would like to use scala unit tests in my current maven/java project because of its BDD features. However, some of my model classes have static CRUDE methods such as Edit.save(). There is any technique where I can mock the implicit calls to inner static methods in Scala?
For regular Java tests this is easily done using PowerMock annotations. My question is how we can do the same using any scala test framework (ScalaTest, Specs2).
I learned that ScalaMock could help with that but the 'GeneratedMockFactory' trait is not present on their maven dependency (listed bellow). It seems to work only on SBT projects.
My scala test libraries:
<dependency>
<groupId>org.scala-lang</groupId>
<artifactId>scala-library</artifactId>
<version>2.10.0</version>
</dependency>
<dependency>
<groupId>org.scalatest</groupId>
<artifactId>scalatest_2.10</artifactId>
<version>2.0.M5b</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.scalamock</groupId>
<artifactId>scalamock-scalatest-support_2.10</artifactId>
<version>3.0.1</version>
</dependency>
This is how my scala tests looks like today:
package com.foo.edit
import org.scalatest._
import org.junit.runner.RunWith
import org.scalatest.junit.JUnitRunner
import com.foo.models.Edit
#RunWith(classOf[JUnitRunner]) // This line binds the test with maven-surefire runner
class EditSpec extends FlatSpec with ShouldMatchers {
"Edit" should "update name and website" in {
val document = new util.HashMap[String, String]() {
put("name", "A Name")
put("website", "www.domain.com")
}
// This is the test subject, it's a Java class
val edit = new Edit().modifiers(new util.HashMap[String, String]() {
put("name", "Another Name")
put("website", "http://site.org")
});
val result = edit.merge(document); // Here it calls Edit.save(document)
// Want to stub this.
assert(result.get("name") === "Another Name");
assert(result.get("website") === "http://site.org");
}
}

Related

Cucumber 7 + TestNG - Dynamic tags manipulation before test run start

I use TestNG and Cucumber in my project.
Used versions:
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>7.4.0</version>
</dependency>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-testng</artifactId>
<version>7.0.0</version>
</dependency>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-java</artifactId>
<version>7.0.0</version>
</dependency>
All works fine, but I need to make some dynamic tags manipulation before test run start.
To be more specific - I need to add additional filter tag expressions based on some conditions of environment.
Current implementation is AbstractTestNGCucumberTests class with a TestNGCucumberRunner field.
The place where I need to change/add some lines of code is in the constructor of TestNGCucumberRunner class, since the runner initializes and starts the test run right there.
Problem is that almost all classes in cucumber-testng dependency are not public and final. So I cannot extend classes and override some logic.
Only way I see is to duplicate all the classes in dependency to my source folder what is a weird and dumb idea.
Is there any tips how can I achieve my goal ?
When extending AbstractTestNGCucumberTests you can filter the output from scenarios()
public class RunCucumberTest extends AbstractTestNGCucumberTests {
#DataProvider(parallel = true)
#Override
public Object[][] scenarios() {
Object[][] scenarios = super.scenarios();
// Do filtering here
return scenarios;
}
}
Because the data provider is used to invoke the test methode you can safely cast the first array element to PickleWrapper and use the pickleWrapper.getPickle().get tags() to access the tags.

Why does using mockito-junit-jupiter and mockito-inline together in Groovy, results in InvalidUseOfMatchersException?

I have a problem with using mockito-junit-jupiter and mockito-inline together in my groovy code, for testing static classes and methods. Hopefully someone can help me.
My current situation is: I am using Groovy, Junit5 and Mockito.
The important parts in my pom.xml are the following:
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
<version>2.5.8</version>
<type>pom</type>
<exclusions>
<exclusion>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-testng</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- Test dependencies -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>${junit5.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.15.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<version>3.8.0</version>
<scope>test</scope>
</dependency>
My Tests are working fine. Now I heard, that I can write tests for static classes and methods. Therefor I read a few posts and in all of them they tell me, that I need to add following dependency on top in my pom and i can test statics:
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>
<version>3.8.0</version>
<scope>test</scope>
</dependency>
When I do that, I can use Mockito.mockStatic in new tests, but now all my other tests, where I use Mockito are broken, as following error message appears: org.mockito.exceptions.misusing.InvalidUseOfMatchersException
But these lines in which I allegedly use wrong argument matchers, I didnt change and they still look totally fine for me. For example: Mockito.when(pipelineConfigLoaderMock.loadResource(Mockito.any(ResourceDefinition.class))).thenThrow(new LoadingResourceFailedException("Mocked Exception"))
Can someone tell me how to use mockito-junit-jupiter along with mockito-inline in groovy? Do i use wrong dependencies or wrong versions which are not compatible together? The exception InvalidUseOfMatchersException, that I receive here, doesn't seem to show the real problem. I already tried groovy 2.5.14 as well, but nothing changed.
Thx in advance
Edit:
Here is a example of an unit-test, which works before I added 'mockito-inline' to my pom:
#Test
#DisplayName("getMyself - handle 404 NOT_FOUND")
void testGetMyself_HandleNotFoundResponse() {
when(simpleRestClientMock.execute(any(RequestEntity.class))).thenAnswer(new Answer<ResponseEntity>() {
#Override
ResponseEntity answer(InvocationOnMock invocation) {
return new ResponseEntity("AnyResponse", HttpStatus.NOT_FOUND)
}
})
assertThatThrownBy({ ->
underTest.getMyself()
}).isInstanceOf(RuntimeException.class).hasMessage("Unknown status: 404 NOT_FOUND")
}
After adding the dependency to the pom I receive following error:
org.mockito.exceptions.misusing.InvalidUseOfMatchersException:
Invalid use of argument matchers!
0 matchers expected, 1 recorded:
-> at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCallStatic(CallSiteArray.java:55)
This exception may occur if matchers are combined with raw values:
//incorrect:
someMethod(anyObject(), "raw String");
When using matchers, all arguments have to be provided by matchers.
For example:
//correct:
someMethod(anyObject(), eq("String by matcher"));
at com.xxx.xxx.cda.core.http.client.SimpleRestClient.getMetaClass(SimpleRestClient.groovy)
at com.xxx.xxx.cda.pipeline.adapter.JiraAdapterTest.testGetMyself_HandleNotFoundResponse
But adding mockito-inline to my pom, enables me to write an unit-test for static classes, that works, for example:
#Test
void approvalHelperThrowsTimeoutExceptionWithout() {
ApprovalHelper underTest = new ApprovalHelper()
underTest = new ApprovalHelper()
OffsetDateTime startDate = OffsetDateTime.now()
OffsetDateTime stopDate = startDate.minusHours(1)
long incrementer = 0
def closure = { Approval approval ->
incrementer++
return false
}
Mockito.mockStatic(ApprovalHelper).with {theMock ->
theMock.when({ ->
ApprovalHelper.createAndExecTimeoutCommand(Mockito.any(OffsetDateTime.class),
Mockito.any(OffsetDateTime), Mockito.any(Closure.class)) })
.thenThrow(new TimeoutException("Mocked Timeout Exception"))
underTest.aroundApproval(TimeDependent.EMPTY, startDate, stopDate, closure)
assertThat(incrementer).isEqualTo(0L)
}
}
Without 'mockito-inline' dependency I would receive following error:
org.mockito.exceptions.base.MockitoException:
The used MockMaker SubclassByteBuddyMockMaker does not support the creation of static mocks
Mockito's inline mock maker supports static mocks based on the Instrumentation API.
You can simply enable this mock mode, by placing the 'mockito-inline' artifact where you are currently using 'mockito-core'.
Note that Mockito's inline mock maker is not supported on Android.
I hope these examples are helpful to explain my problem.
Best regards
It is too late for your problem but still wanted to put this answer here so that someone else like me might find an answer for their problem.
According to Mockito documentation, Mockito-core and Mockito-inline cannot co-exist in a project. Your mockito-junit-jupiter dependency already depends on mockito-core, thus indirectly causing Mockito-core and Mockito-inline co-exist. If you can eliminate mockito-junit-jupiter dependency and leave only mockito-inline, hopefully it will solve the problem.

PowerMockito mock indirect static method

Class to test
public class Randomer {
public int get() {
return (int) Math.random() + 1;
}
}
The test class
package org.samiron;
import static org.junit.Assert.assertEquals;
import org.junit.runner.RunWith;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.api.support.membermodification.MemberMatcher;
import org.powermock.api.support.membermodification.MemberModifier;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.testng.annotations.Test;
/**
* #author samiron
*
*/
#RunWith(PowerMockRunner.class)
#PrepareForTest({ Randomer.class, Math.class })
public class RandomerTest {
#Test
public void shouldAddUpDieRollsCorrectly() throws Exception {
PowerMockito.spy(Math.class);
MemberModifier.stub(MemberMatcher.method(Math.class, "random")).toReturn(2.0);
Randomer d = new Randomer();
assertEquals(3, d.get());
}
}
Always getting java.lang.AssertionError: expected:<3> but was:<1>
Whats going wrong here? To be honest, every time I came across a situation to mock a static function I try to find a way around instead of wasting time. So need your help to figure out the exact solution.
The sole purpose of the example classes is to demonstrate that Math.random() function is not mocked and therefore not returning the desired value.
General realization
Mocking is a every essential tool while writing tests. Although mocking on instances works quite as expected but mocking static methods seems to be a real complicated with so many combinations of mocking libraries and so many options to support just few simple scenarios. This should be streamlined.
Libraries used:
mockito-all-1.9.5.jar
powermock-mockito-release-full-1.5.1-full.jar
This test passes thereby proving that the static call Math.random() is successfully mocked:
#RunWith(PowerMockRunner.class)
// tell PowerMock about (a) the class you are going to test and (b) the class you are going to 'mock static'
#PrepareForTest({Randomer.class, Math.class })
public class RandomerTest {
#Test
public void shouldAddUpDieRollsCorrectly() throws Exception {
// prepare PowerMock for mocking statics on Math
PowerMockito.mockStatic(Math.class);
// establish an expectation for what Match.random() should return
PowerMockito.when(Math.random()).thenReturn(2.0);
Randomer d = new Randomer();
assertEquals(3, d.get());
}
}
The main differences between this and what you posted in your question are is that I am using ...
PowerMockito.mockStatic(Math.class) and PowerMockito.when(Math.random()).thenReturn(2.0)
... instead of:
PowerMockito.spy(Math.class) and MemberModifier.stub(MemberMatcher.method(Math.class, "random")).toReturn(2.0)
Also, in your OP the example code uses a mixture of JUnit (org.powermock.modules.junit4.PowerMockRunner) and TestNG (org.testng.annotations.Test) whereas in my example I am just using JUnit.
The above has been verified with
junit:4.12
powermock-module-junit4:1.7.0
powermock-api-mockito2:1.7.0
There seems to be a library mismatch here.
In the comments you stated to use the following dependencies (without the convenience of Maven):
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<version>1.9.5</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-mockito-release-full</artifactId>
<version>1.5.1</version>
<scope>test</scope>
</dependency>
I got your code working using these:
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>1.7.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito</artifactId>
<version>1.7.0</version>
<scope>test</scope>
</dependency>

hard time mojo testing with maven-plugin-testing-harness

My plugin mojo test class leverages maven-plugin-test-harness to build the complete maven environment with all pom config, plexus container context and repo access.
The following should all actually work:
the test will refer to a test pom.xml in the plugin project's test resources directory
the mojo will be populated with defaults from the mojo annotations
all specified configuration in the test pom is accepted
the maven project object is initialised
all dependencies from the repo are available
the tests should pass in IntelliJ IDE as well as Maven on CI server
Because of the lack of concrete working examples I've been trying many different fixes using what I've collected from SO, and other blogs or articles online.
I am currently struggling to get the maven to resolve the artifacts. While I've got the dependency list from the maven project object, the artifact list is empty.
This is what I've built up by dissecting AbstractMojoTestCase.
I can't use MojoRule because JUnit5 doesn't use #Rules anymore.
Plus, some of the maven API calls are deprecated, but I couldn't find a new implementation. I think it won't come until mvn4. See the quote below.
#BeforeEach
protected void setUp() throws Exception {
super.setUp();
cleanUp();
ClassLoader classLoader = getClass().getClassLoader();
URL url = classLoader.getResource(TEST_POM);
if (url == null) {
throw new MojoExecutionException(String.format(
"Cannot locate %s", TEST_POM));
}
File pom = new File(url.getFile());
//noinspection deprecation - wait on maven-plugin-testing-harness update
MavenSettingsBuilder mavenSettingsBuilder = (MavenSettingsBuilder)
getContainer().lookup(MavenSettingsBuilder.ROLE);
Settings settings = mavenSettingsBuilder.buildSettings();
MavenExecutionRequest request = new DefaultMavenExecutionRequest();
request.setPom(pom);
request.setLocalRepositoryPath(settings.getLocalRepository());
MavenExecutionRequestPopulator populator =
getContainer().lookup(MavenExecutionRequestPopulator.class);
populator.populateDefaults(request);
DefaultMaven maven = (DefaultMaven) getContainer().lookup(Maven.class);
DefaultRepositorySystemSession repoSession =
(DefaultRepositorySystemSession)
maven.newRepositorySession(request);
LocalRepository localRepository = new LocalRepository(
request.getLocalRepository().getBasedir());
SimpleLocalRepositoryManagerFactory factory =
new SimpleLocalRepositoryManagerFactory();
LocalRepositoryManager localRepositoryManager =
factory.newInstance(repoSession, localRepository);
repoSession.setLocalRepositoryManager(localRepositoryManager);
ProjectBuildingRequest buildingRequest =
request.getProjectBuildingRequest()
.setRepositorySession(repoSession)
.setResolveDependencies(true);
ProjectBuilder projectBuilder = lookup(ProjectBuilder.class);
MavenProject project =
projectBuilder.build(pom, buildingRequest).getProject();
//noinspection deprecation - wait on maven-plugin-testing-harness update
MavenSession session = new MavenSession(getContainer(), repoSession,
request, new DefaultMavenExecutionResult());
session.setCurrentProject(project);
session.setProjects(Collections.singletonList(project));
request.setSystemProperties(System.getProperties());
testMojo = (GenerateConfig) lookupConfiguredMojo(session,
newMojoExecution("configure"));
copyTestProjectResourcesToTarget(getContainer(), project, session);
}
[UPDATE 2017-07-27]: actually this now solves most of my problems.
Only a couple of minor issues now:
the code to grab the settings.xml is marked as #Deprecated so I assume there is a better way of doing it (using the MavenSettingsBuilder.buildSettings())
probably quite a lot of the setup code is duplicating process that occurs anyway when running in native maven, but is required to run with JUnit in IntelliJ.
[UPDATE 2017-08-01]: test now needs to access some property files which would be on the classpath in a live environment in the target/classes dir.
Logically they are test resources in my maven-plugin project, so I have included them under the same directory as the test pom.xml in src/test/resources/my-test-project dir.
That didn't work, so I also tried src/test/resources/my-test-project/src/main/resources but that's also not good.
I am having a hard time establishing what exactly is on the plugin's classpath during the test, or working out how to cause it to be constructed correctly.
[UPDATE 2017-08-02]: although I've answered my own question (as opposed to extending this question), this whole thing isn't finished yet so I'm not marking this as answered quite yet.
And just for the record, these are the dependencies required:
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.0.0-M4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
<version>4.12.0-M4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-plugin-api</artifactId>
<version>3.5.0</version>
</dependency>
<dependency>
<groupId>org.apache.maven.plugin-tools</groupId>
<artifactId>maven-plugin-annotations</artifactId>
<version>3.5</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.maven.plugin-testing</groupId>
<artifactId>maven-plugin-testing-harness</artifactId>
<version>3.3.0</version>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-container-default</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-core</artifactId>
<version>3.5.0</version>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-compat</artifactId>
<version>3.5.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.twdata.maven</groupId>
<artifactId>mojo-executor</artifactId>
<version>2.3.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>3.0.2</version>
<scope>test</scope>
</dependency>
[UPDATE 2017-08-09]:
I have to add some more functionality and discovered that the test was fine if the dependency it wanted to unpack was in the local repo already, but if not, it won't fetch it.
I now need to determine how to instruct maven to fetch the dependency from the remote repo.
I tried launching the dependency plugin and invoking resolve in the test setup, but it dies badly, I think there must be a simpler way.
No need for so much complexity.
I can't use MojoRule because JUnit5 doesn't use #Rules anymore.
Since version 3.2 you don't need to use #MojoRule, 7 years ago. Just follow the three steps below:
Your test class should extend AbstractMojoTestCase
Before your tests, call super.setUp()
Perform a lookup for your mojo:
MyMojo myMojo = (MyMojo) super.lookupMojo("myGoal", "src/test/resources/its/my-test-mojo.pom.xml");
With that, you can work with Junit 5, Mockito, etc, with no overhead.
Some comments in the maven source code for MavenProject said
With changes during 3.2.2 release MavenProject is closer to being immutable after construction with the removal of all components from this class, and the upfront construction taken care of entirely by the #{ProjectBuilder}. There is still the issue of having to run the lifecycle in order to find all the compile source roots and resource directories but I hope to take care of this during the Maven 4.0 release (jvz).
I figure this whole maven plugin integration test thing is not going to work until then... and so looking around, I found a great blog entry on invoking plugins. So I invoked the maven-resources-plugin directly to get it to copy across what it was meant to. That's what the copyTestProjectResourcesToTarget() call does.
private void copyTestProjectResourcesToTarget(PlexusContainer container,
MavenProject project,
MavenSession session)
throws ComponentLookupException, MojoExecutionException {
logger.fine("generateConfig dependencies: ");
project.getDependencies().forEach(d -> logger.fine(d.getArtifactId()));
Optional<Dependency> resourcesPluginDepOpt =
project.getDependencies().stream()
.filter(d -> Objects.equals(d.getArtifactId(),
MAVEN_RESOURCES_ARTIFACT_ID))
.findFirst();
// don't want to define the version here so we read it from what we have
if (!resourcesPluginDepOpt.isPresent()) {
throw new MojoExecutionException("Require " +
MAVEN_RESOURCES_ARTIFACT_ID);
}
Plugin resourcePlugin = MojoExecutor.plugin(
MojoExecutor.groupId(MAVEN_RESOURCES_GROUP_ID),
MojoExecutor.artifactId(MAVEN_RESOURCES_ARTIFACT_ID),
MojoExecutor.version(resourcesPluginDepOpt.get().getVersion()));
MojoExecutor.executeMojo(resourcePlugin,
MojoExecutor.goal("resources"),
MojoExecutor.configuration(),
MojoExecutor.executionEnvironment(
project, session,
container.lookup(BuildPluginManager.class)));
}
I found the solution to fetching dependencies from the remote repository.
Working with the maven internals like this and judging from the deprecated classes and the amount of duplicated functionality, it gives me the strong impression that maven v4 will make this redundant.
One glitch with this setup routine is that it creates a local repository directory tree in the maven project directory. This is obviously not desirable but will need some more tweaking to solve.
#BeforeEach
public void setUp() throws Exception {
super.setUp();
ClassLoader classLoader = getClass().getClassLoader();
URL url = classLoader.getResource(TEST_POM);
if (url == null) {
throw new MojoExecutionException(String.format(
"Cannot locate %s", TEST_POM));
}
File pom = new File(url.getFile());
Settings settings = getMavenSettings();
if (settings.getLocalRepository() == null) {
settings.setLocalRepository(
org.apache.maven.repository.RepositorySystem
.defaultUserLocalRepository.getAbsolutePath());
}
MavenExecutionRequest request = new DefaultMavenExecutionRequest();
request.setPom(pom);
ArtifactRepository artifactRepository =
new org.apache.maven.artifact.repository.
DefaultArtifactRepository(
"id", settings.getLocalRepository(),
new DefaultRepositoryLayout());
request.setLocalRepository(artifactRepository);
MavenExecutionRequestPopulator populator =
getContainer().lookup(MavenExecutionRequestPopulator.class);
populator.populateFromSettings(request, settings);
DefaultMaven maven = (DefaultMaven)
getContainer().lookup(Maven.class);
DefaultRepositorySystemSession repositorySystemSession =
(DefaultRepositorySystemSession)
maven.newRepositorySession(request);
SimpleLocalRepositoryManagerFactory factory =
new SimpleLocalRepositoryManagerFactory();
LocalRepositoryManager localRepositoryManager =
factory.newInstance(repositorySystemSession,
new LocalRepository(settings.getLocalRepository()));
repositorySystemSession.setLocalRepositoryManager(
localRepositoryManager);
ProjectBuildingRequest buildingRequest =
request.getProjectBuildingRequest()
.setRepositorySession(repositorySystemSession)
.setResolveDependencies(true);
ProjectBuilder projectBuilder = lookup(ProjectBuilder.class);
ProjectBuildingResult projectBuildingResult =
projectBuilder.build(pom, buildingRequest);
MavenProject project = projectBuildingResult.getProject();
MavenSession session = new MavenSession(getContainer(),
repositorySystemSession, request,
new DefaultMavenExecutionResult());
session.setCurrentProject(project);
session.setProjects(Collections.singletonList(project));
request.setSystemProperties(System.getProperties());
testMojo = (GenerateConfig) lookupConfiguredMojo(session,
newMojoExecution("configure"));
testMojo.getLog().debug(String.format("localRepo = %s",
request.getLocalRepository()));
copyTestProjectResourcesToTarget(getContainer(), project, session);
resolveConfigurationFromRepo(repositorySystemSession, project);
}
private Settings getMavenSettings()
throws ComponentLookupException,
IOException,
XmlPullParserException {
org.apache.maven.settings.MavenSettingsBuilder mavenSettingsBuilder
= (org.apache.maven.settings.MavenSettingsBuilder)
getContainer().lookup(
org.apache.maven.settings.MavenSettingsBuilder.ROLE);
return mavenSettingsBuilder.buildSettings();
}
/**
* This is ugly but there seems to be no other way to accomplish it. The
* artifact that the mojo finds on its own will not resolve to a jar file
* on its own in the test harness. So we use aether to resolve it, by
* cloning the maven default artifact into an aether artifact and feeding
* an artifact request to the repo system obtained by the aether service
* locator.
*/
private void resolveConfigurationFromRepo(
DefaultRepositorySystemSession repositorySystemSession,
MavenProject project)
throws ArtifactResolutionException, MojoExecutionException {
org.apache.maven.artifact.Artifact defaultArtifact =
testMojo.getConfigArtifact();
Artifact artifact = new DefaultArtifact(
defaultArtifact.getGroupId(),
defaultArtifact.getArtifactId(),
null,
defaultArtifact.getType(),
defaultArtifact.getVersion());
List<RemoteRepository> remoteArtifactRepositories =
project.getRemoteProjectRepositories();
DefaultServiceLocator locator =
MavenRepositorySystemUtils.newServiceLocator();
locator.addService(RepositoryConnectorFactory.class,
BasicRepositoryConnectorFactory.class);
locator.addService(TransporterFactory.class,
FileTransporterFactory.class);
locator.addService(TransporterFactory.class,
HttpTransporterFactory.class);
RepositorySystem repositorySystem = locator.getService(
RepositorySystem.class);
ArtifactRequest artifactRequest = new ArtifactRequest();
artifactRequest.setArtifact(artifact);
artifactRequest.setRepositories(remoteArtifactRepositories);
ArtifactResult result = repositorySystem.resolveArtifact(
repositorySystemSession, artifactRequest);
defaultArtifact.setFile(result.getArtifact().getFile());
testMojo.getLog().debug( "Resolved artifact " + artifact + " to " +
result.getArtifact().getFile() + " from "
+ result.getRepository() );
}
/**
* Need manual copy of resources because only parts of the maven lifecycle
* happen automatically with this test harness.
*/
private void copyTestProjectResourcesToTarget(PlexusContainer container,
MavenProject project,
MavenSession session)
throws ComponentLookupException, MojoExecutionException {
Optional<Dependency> resourcesPluginDepOpt =
project.getDependencies().stream()
.filter(d -> Objects.equals(d.getArtifactId(),
MAVEN_RESOURCES_ARTIFACT_ID))
.findFirst();
// don't want to define the version here so we read it from what we have
if (!resourcesPluginDepOpt.isPresent()) {
throw new MojoExecutionException("Require " +
MAVEN_RESOURCES_ARTIFACT_ID);
}
Plugin resourcePlugin = MojoExecutor.plugin(
MojoExecutor.groupId(MAVEN_PLUGINS_GROUP_ID),
MojoExecutor.artifactId(MAVEN_RESOURCES_ARTIFACT_ID),
MojoExecutor.version(resourcesPluginDepOpt.get().getVersion()));
MojoExecutor.executeMojo(resourcePlugin,
MojoExecutor.goal("resources"),
MojoExecutor.configuration(),
MojoExecutor.executionEnvironment(
project, session,
container.lookup(BuildPluginManager.class)));
}
and here are the packages used, quite important to use the classes from the right package but easily confused:
import org.apache.maven.DefaultMaven;
import org.apache.maven.Maven;
import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.artifact.repository.layout.DefaultRepositoryLayout;
import org.apache.maven.execution.DefaultMavenExecutionRequest;
import org.apache.maven.execution.DefaultMavenExecutionResult;
import org.apache.maven.execution.MavenExecutionRequest;
import org.apache.maven.execution.MavenExecutionRequestPopulator;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.model.Dependency;
import org.apache.maven.model.Plugin;
import org.apache.maven.plugin.BuildPluginManager;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugin.testing.AbstractMojoTestCase;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.ProjectBuilder;
import org.apache.maven.project.ProjectBuildingRequest;
import org.apache.maven.project.ProjectBuildingResult;
import org.apache.maven.repository.internal.MavenRepositorySystemUtils;
import org.apache.maven.settings.Settings;
import org.codehaus.plexus.PlexusContainer;
import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
import org.eclipse.aether.DefaultRepositorySystemSession;
import org.eclipse.aether.RepositorySystem;
import org.eclipse.aether.artifact.Artifact;
import org.eclipse.aether.artifact.DefaultArtifact;
import org.eclipse.aether.connector.basic.BasicRepositoryConnectorFactory;
import org.eclipse.aether.impl.DefaultServiceLocator;
import org.eclipse.aether.internal.impl.SimpleLocalRepositoryManagerFactory;
import org.eclipse.aether.repository.LocalRepository;
import org.eclipse.aether.repository.LocalRepositoryManager;
import org.eclipse.aether.repository.RemoteRepository;
import org.eclipse.aether.resolution.ArtifactRequest;
import org.eclipse.aether.resolution.ArtifactResolutionException;
import org.eclipse.aether.resolution.ArtifactResult;
import org.eclipse.aether.spi.connector.RepositoryConnectorFactory;
import org.eclipse.aether.spi.connector.transport.TransporterFactory;
import org.eclipse.aether.transport.file.FileTransporterFactory;
import org.eclipse.aether.transport.http.HttpTransporterFactory;

How to unset environment variable before testing in maven

I have some unit test which depends on environment variable.
I'd like to unset these environment variable before testing using maven.
Question: How can I achieve that?
Unfortunately, in this case you cannot use the environmentVariables option of the Maven Surefire Plugin, mainly because it would only work to add new environment variables but not override (or reset, which is actually equals to override to empty value) an existing variable.
Also note: an ant run wrapped in Maven and executed before the test would not work either.
The proposed solution is based on Java, especially on this approach proposed in another SO post. Although not advisable for application code, this hack may be acceptable for test code.
You could add the following class to your test code (under src/test/java):
package com.sample;
import java.lang.reflect.Field;
import java.util.Map;
public class EnvHack {
#SuppressWarnings("unchecked")
public static void resetEnvironmentVariable(String name, String value) throws Exception {
Class<?> processEnvironmentClass = Class.forName("java.lang.ProcessEnvironment");
Field theEnvironmentField = processEnvironmentClass.getDeclaredField("theEnvironment");
theEnvironmentField.setAccessible(true);
Map<String, String> env = (Map<String, String>) theEnvironmentField.get(null);
env.put(name, value);
Field theCaseInsensitiveEnvironmentField = processEnvironmentClass
.getDeclaredField("theCaseInsensitiveEnvironment");
theCaseInsensitiveEnvironmentField.setAccessible(true);
Map<String, String> cienv = (Map<String, String>) theCaseInsensitiveEnvironmentField.get(null);
cienv.put(name, value);
}
public static void main(String[] args) throws Exception {
resetEnvironmentVariable("test_prop", "test_value");
}
}
The class above is basically hacking Java API to change in memory the values of the environment variables. As such, they can be set to different values, reset and even unset actually (remove it from the map).
Since the class is now part of your test code, you have several options:
Use the class in the #Before methods (or #BeforeClass, with a certain difference) of a certain JUnit test case (before every JUnit method of the concerned class)
Use it within a JUnit test method (custom and narrowed usage)
Run its main method before any executed JUnit test (in a more global way) as explained below (and probably answering the question, even though other scenarios are also worth to mention, imho).
Let's have a look at each possible solution.
Use the class in the #Before methods of a certain JUnit test case
package com.sample;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
public class MainTest {
#Before
public void init() throws Exception {
EnvHack.resetEnvironmentVariable("test_prop", "test_value");
}
#Test
public void testEnvProperty() throws Exception {
String s = System.getenv("test_prop");
Assert.assertEquals(s, "test_value");
}
}
This solution can be used per test class and when a set of tests (methods) share the same requirements (suggestion: if they don't, it may be an hint, probably some method should be moved out).
Use it within a JUnit test method
package com.sample;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
public class MainTest {
#Before
public void init() throws Exception {
EnvHack.resetEnvironmentVariable("test_prop", "test_value");
}
#Test
public void testEnvProperty() throws Exception {
EnvHack.resetEnvironmentVariable("test_prop", "test_value2");
String s = System.getenv("test_prop");
Assert.assertEquals(s, "test_value2");
}
}
This solution gives the highest freedom: you can play with exactly the concerned variable exactly where required, although may suffer of code duplication it could also enforce tests isolation.
Run its main method before any executed JUnit test
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.4.0</version>
<executions>
<execution>
<phase>process-test-classes</phase>
<goals>
<goal>java</goal>
</goals>
<configuration>
<mainClass>com.sample.EnvHack</mainClass>
<classpathScope>test</classpathScope>
</configuration>
</execution>
</executions>
</plugin>
Note what we are doing in this case:
We are invoking the java goal of the Exec Maven Plugin to actually invoke the mainClass of our env hack class.
We are invoking it with classPathScope set to test in order to make it visible to the Enforcer Plugin
We are running it as part of the process-test-classes phase just to make sure it is executed before the test phase and hence before any test.
This solution centralizes the whole prepare environment procedure, once for all tests.
On the other side, you may also consider to use mocking in your tests. This is not a centralized option (unless you code it) but could give further hints and hence worth to mention. Here is a sample code resetting an environment variable via PowerMock
package com.sample;
import org.junit.*;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
#RunWith(PowerMockRunner.class)
#PrepareForTest(System.class)
public class EnvTest {
#Before
public void init() {
PowerMockito.mockStatic(System.class);
Mockito.when(System.getenv("TEST_PROP")).thenReturn("TEST_VALUE");
}
#Test
public void test() {
String var = System.getenv("TEST_PROP");
Assert.assertEquals("TEST_VALUE", var);
}
}
This is similar to the first and second approach proposed above.
Note that to make it work you need to add the following dependencies to your POM:
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito</artifactId>
<version>1.5</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-core</artifactId>
<version>1.5</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>1.5</version>
<scope>test</scope>
</dependency>
</dependencies>
General note: indeed having tests which are not fully isolated and depends on the existence (or the absence) of an environment variable is not advisable. You may run into maintenance issues or have more nasty troubleshooting, for colleagues or for your future yourself. So, if you really need it, better to properly document it.

Categories

Resources