Added option of multiple test base classes

without this change the user was forced to use a single base class for all of the generated tests. It could become problematic after some time.

With this change we provide a range of options of providing different base classes for different contracts.

fixes #16
This commit is contained in:
Marcin Grzejszczak
2016-09-23 15:39:12 +02:00
parent 82a3da2b37
commit e8917940ec
33 changed files with 746 additions and 33 deletions

View File

@@ -0,0 +1,53 @@
package org.springframework.cloud.contract.maven.verifier;
/**
* Represents a single mapping of regex on package where contracts reside
* to the FQN of the base test class
*
* @author Marcin Grzejszczak
* @since 1.0.0
*/
public class BaseClassMapping {
private String contractPackageRegex;
private String baseClassFQN;
public String getContractPackageRegex() {
return this.contractPackageRegex;
}
public void setContractPackageRegex(String contractPackageRegex) {
this.contractPackageRegex = contractPackageRegex;
}
public String getBaseClassFQN() {
return this.baseClassFQN;
}
public void setBaseClassFQN(String baseClassFQN) {
this.baseClassFQN = baseClassFQN;
}
@Override public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
BaseClassMapping that = (BaseClassMapping) o;
if (this.contractPackageRegex != null ?
!this.contractPackageRegex.equals(that.contractPackageRegex) :
that.contractPackageRegex != null)
return false;
return this.baseClassFQN != null ?
this.baseClassFQN.equals(that.baseClassFQN) :
that.baseClassFQN == null;
}
@Override public int hashCode() {
int result = this.contractPackageRegex != null ? this.contractPackageRegex.hashCode() : 0;
result = 31 * result + (this.baseClassFQN != null ? this.baseClassFQN.hashCode() : 0);
return result;
}
}

View File

@@ -134,7 +134,6 @@ public class ConvertMojo extends AbstractMojo {
new CopyContracts(this.project, this.mavenSession, this.mavenResourcesFiltering)
.copy(contractsDirectory, this.outputDirectory);
config.setContractsDslDir(isInsideProject() ? contractsDirectory : this.source);
config.setStubsOutputDir(
isInsideProject() ? new File(this.outputDirectory, "mappings") : this.destination);

View File

@@ -16,8 +16,9 @@
package org.springframework.cloud.contract.maven.verifier;
import java.io.File;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.inject.Inject;
import org.apache.maven.model.Dependency;
@@ -140,6 +141,30 @@ public class GenerateTestsMojo extends AbstractMojo {
@Parameter(property = "contractsWorkOffline", defaultValue = "false")
private boolean contractsWorkOffline;
/**
* A package that contains all the base clases for generated tests. If your contract resides in a location
* {@code src/test/resources/contracts/com/example/v1/} and you provide the {@code packageWithBaseClasses}
* value to {@code com.example.contracts.base} then we will search for a test source file that will
* have the package {@code com.example.contracts.base} and name {@code ExampleV1Base}. As you can see
* it will take the two last folders to and attach {@code Base} to its name.
*/
@Parameter(property = "packageWithBaseClasses")
private String packageWithBaseClasses;
/**
* A way to override any base class mappings. The keys are regular expressions on the package name of the contract
* and the values FQN to a base class for that given expression.
* </p>
* Example of a mapping
* </p>
* {@code .*.com.example.v1..*} -> {@code com.example.SomeBaseClass}
* </p>
* When a contract's package matches the provided regular expression then extending class will be the one
* provided in the map - in this case {@code com.example.SomeBaseClass}
*/
@Parameter(property = "baseClassMappings")
private List<BaseClassMapping> baseClassMappings;
private final AetherStubDownloaderFactory aetherStubDownloaderFactory;
@Inject
@@ -175,6 +200,10 @@ public class GenerateTestsMojo extends AbstractMojo {
config.setIgnoredFiles(this.ignoredFiles);
config.setExcludedFiles(this.excludedFiles);
config.setAssertJsonSize(this.assertJsonSize);
config.setPackageWithBaseClasses(this.packageWithBaseClasses);
if (this.baseClassMappings != null) {
config.setBaseClassMappings(mappingsToMap());
}
this.project.addTestCompileSourceRoot(this.generatedTestSourcesDir.getAbsolutePath());
if (getLog().isInfoEnabled()) {
getLog().info(
@@ -195,6 +224,17 @@ public class GenerateTestsMojo extends AbstractMojo {
}
}
public Map<String, String> mappingsToMap() {
Map<String, String> map = new HashMap<>();
if (this.baseClassMappings == null) {
return map;
}
for (BaseClassMapping mapping : this.baseClassMappings) {
map.put(mapping.getContractPackageRegex(), mapping.getBaseClassFQN());
}
return map;
}
public List<String> getExcludedFiles() {
return this.excludedFiles;
}

View File

@@ -162,4 +162,51 @@ public class PluginUnitTest {
assertFilesPresent(basedir, "target/generated-test-sources/contracts/org/springframework/cloud/contract/verifier/tests/com/example/server/client1/ContractsTest.java");
}
@Test
public void shouldGenerateContractTestsWithBaseClassResolvedFromConvention() throws Exception {
File basedir = this.resources.getBasedir("basic-generated-baseclass");
this.maven.executeMojo(basedir, "generateTests", newParameter("testFramework", "JUNIT"));
String path = "target/generated-test-sources/contracts/org/springframework/cloud/contract/verifier/tests/hello/V1Test.java";
assertFilesPresent(basedir, path);
File test = new File(basedir, path);
then(FileUtils.readFileToString(test)).contains("extends HelloV1Base").contains("import hello.HelloV1Base");
}
@Test
public void shouldGenerateContractTestsWithBaseClassResolvedFromConventionForSpock() throws Exception {
File basedir = this.resources.getBasedir("basic-generated-baseclass");
this.maven.executeMojo(basedir, "generateTests", newParameter("testFramework", "SPOCK"));
String path = "target/generated-test-sources/contracts/org/springframework/cloud/contract/verifier/tests/hello/V1Spec.groovy";
assertFilesPresent(basedir, path);
File test = new File(basedir, path);
then(FileUtils.readFileToString(test)).contains("extends HelloV1Base").contains("import hello.HelloV1Base");
}
@Test
public void shouldGenerateContractTestsWithBaseClassResolvedFromMapping() throws Exception {
File basedir = this.resources.getBasedir("basic-baseclass-from-mappings");
this.maven.executeMojo(basedir, "generateTests", newParameter("testFramework", "JUNIT"));
String path = "target/generated-test-sources/contracts/org/springframework/cloud/contract/verifier/tests/com/hello/V1Test.java";
assertFilesPresent(basedir, path);
File test = new File(basedir, path);
then(FileUtils.readFileToString(test)).contains("extends TestBase").contains("import com.example.TestBase");
}
@Test
public void shouldGenerateContractTestsWithBaseClassResolvedFromMappingNameForSpock() throws Exception {
File basedir = this.resources.getBasedir("basic-baseclass-from-mappings");
this.maven.executeMojo(basedir, "generateTests", newParameter("testFramework", "SPOCK"));
String path = "target/generated-test-sources/contracts/org/springframework/cloud/contract/verifier/tests/com/hello/V1Spec.groovy";
assertFilesPresent(basedir, path);
File test = new File(basedir, path);
then(FileUtils.readFileToString(test)).contains("extends TestBase").contains("import com.example.TestBase");
}
}

View File

@@ -0,0 +1,48 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright 2013-2016 the original author or authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework.cloud.verifier.sample</groupId>
<artifactId>sample-project</artifactId>
<version>0.1</version>
<build>
<plugins>
<!-- tag::mapping[] -->
<plugin>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-contract-maven-plugin</artifactId>
<configuration>
<baseClassForTests>com.example.FooBase</baseClassForTests>
<baseClassMappings>
<baseClassMapping>
<contractPackageRegex>.*com.*</contractPackageRegex>
<baseClassFQN>com.example.TestBase</baseClassFQN>
</baseClassMapping >
</baseClassMappings>
</configuration>
</plugin>
<!-- end::mapping[] -->
</plugins>
</build>
</project>

View File

@@ -0,0 +1,37 @@
/**
*
* Copyright 2013-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
org.springframework.cloud.contract.spec.Contract.make {
label 'some_label'
input {
messageFrom('jms:input')
messageBody([
bookName: 'foo'
])
messageHeaders {
header('sample', 'header')
}
}
outputMessage {
sentTo('jms:output')
body([
bookName: 'foo'
])
headers {
header('BOOK-NAME', 'foo')
}
}
}

View File

@@ -0,0 +1,34 @@
/**
*
* Copyright 2013-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
org.springframework.cloud.contract.spec.Contract.make {
request {
method 'POST'
url('/users') {
}
headers {
header 'Content-Type': 'application/json'
}
body '''{ "login" : "john", "name": "John The Contract" }'''
}
response {
status 200
headers {
header 'Location': '/users/john'
}
}
}

View File

@@ -0,0 +1,34 @@
/**
*
* Copyright 2013-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
org.springframework.cloud.contract.spec.Contract.make {
request {
method 'POST'
url('/users') {
}
headers {
header 'Content-Type': 'application/json'
}
}
response {
status 200
headers {
header 'Location': '/users/john'
}
body '''{ "list" : [ "login", "john", "name", "John The Contract" ] }'''
}
}

View File

@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright 2013-2016 the original author or authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework.cloud.verifier.sample</groupId>
<artifactId>sample-project</artifactId>
<version>0.1</version>
<build>
<plugins>
<!-- tag::convention[] -->
<plugin>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-contract-maven-plugin</artifactId>
<configuration>
<packageWithBaseClasses>hello</packageWithBaseClasses>
</configuration>
</plugin>
<!-- tag::convention[] -->
</plugins>
</build>
</project>

View File

@@ -0,0 +1,37 @@
/**
*
* Copyright 2013-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
org.springframework.cloud.contract.spec.Contract.make {
label 'some_label'
input {
messageFrom('jms:input')
messageBody([
bookName: 'foo'
])
messageHeaders {
header('sample', 'header')
}
}
outputMessage {
sentTo('jms:output')
body([
bookName: 'foo'
])
headers {
header('BOOK-NAME', 'foo')
}
}
}

View File

@@ -0,0 +1,34 @@
/**
*
* Copyright 2013-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
org.springframework.cloud.contract.spec.Contract.make {
request {
method 'POST'
url('/users') {
}
headers {
header 'Content-Type': 'application/json'
}
body '''{ "login" : "john", "name": "John The Contract" }'''
}
response {
status 200
headers {
header 'Location': '/users/john'
}
}
}

View File

@@ -0,0 +1,34 @@
/**
*
* Copyright 2013-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
org.springframework.cloud.contract.spec.Contract.make {
request {
method 'POST'
url('/users') {
}
headers {
header 'Content-Type': 'application/json'
}
}
response {
status 200
headers {
header 'Location': '/users/john'
}
body '''{ "list" : [ "login", "john", "name", "John The Contract" ] }'''
}
}