Can I avoid a dependency cycle with one edge being a test dependency?
Asked Answered
N

2

10

Consider a testCycle parent with modules DummyCore and TestFramework.

TestFramework depends on DummyCore, and DummyCore has a test dedepency on TestFramework.

Building and testing each module independently maven has no problems. But mvn test the parents testCycle results in:

    The projects in the reactor contain a cyclic reference: Edge between 'Vertex{label='com.mysimpatico:TestFramework:1.0-SNAPSHOT'}' and 'Vertex{label='org.apache:DummyCore:1.0-SNAPSHOT'}' introduces to cycle in the graph org.apache:DummyCore:1.0-SNAPSHOT --> com.mysimpatico:TestFramework:1.0-SNAPSHOT --> org.apache:DummyCore:1.0-SNAPSHOT -> [Help 1]

To see the full stack trace of the errors, re-run Maven with the -e switch.
Re-run Maven using the -X switch to enable full debug logging.

For more information about the errors and possible solutions, please read the following articles:
[Help 1] http://cwiki.apache.org/confluence/display/MAVEN/ProjectCycleException

To reproduce:

wget http://dp4j.sf.net/debug/testCycle.zip
unzip testCycle.zip
cd testCycle; mvn test 

My expectation was that maven would build DummyCore src and then coming to compile the tests will compile TestFramework src, which doesn't depend on DummyCore. At this stage it would have compiled DummyCore src + tests, and TestFramework src. Finally it will compile DummyCore tests too. Is there a way to tell maven to do this? If not, how would you work around this?

Move the tests in DummyCore into a module of its own that depends on DummyCore and TestFramework? I'd be doing that just to satisfy maven.

Nitza answered 17/5, 2011 at 17:10 Comment(1)
In my experience, cyclic dependencies always shout that there's a problem with the design. It doesn't matter if the cycle is in a jar, a package or a class.Ramon
C
4

You can't resolve this conflict with Maven or with any other build tool. It's not a build tool issue, it is an architectural flaw and can only be addressed through refactoring.

Two options come immediately to mind:

1) Create a new module called "test_common" that contains the stuff that both TestFramework need and DummyCore need. The make test_common a dependency of both of those modules.

2) Move the stuff that TestFramework needs from DummyCore into TestFramework. Then TestFramework depends on nothing and DummyCore depends on TestFramework.

There are many ways to solve this, but circular inter-module dependencies are a big time NO-NO regardless of language or build tool.

Cervix answered 10/2, 2012 at 2:48 Comment(3)
it's a build tool issue. I explained how a smarter Maven could have avoided the dependency cycle.Nitza
No, it's a design flaw. Whatever code depends on both modules needs to exist in its own module. It's not maven you're satisfying, it's your dependency graph.Tum
It is a build tool issue. On a practical level you want to have your module's tests within the same module, however it is logically separate and there is no reason why this should fail to compile. In fact, Gradle can handle this scenario.Charybdis
E
0

Yet I would agree that cyle dependency is bad design, there is one case I still tried to make one and it is precisely as OP asked: testing. I have DSL test classes based on my model which makes me able to write things like

Book book = new Book()
assertThat(book).hasNoAuthor(); // "hasNoAuthor()" is AssertJ base assertions
verify(book, never()).addAuthor(anyAuthor()); // "anyAuthor()" is mockito based matchers

There DSL objects are usefull both to test my domain module where my Book object is and other modules that consumes my domain module.

So I tried creating a domain-test module along with the existing domain module but

  • unit tests in domain depends on domain-test DSL classes
  • DSL classes in domain-test depends on domain

... Here comes the cycle.

The only way to break this cycle is to extract the unit tests out of the domain module:

domain-unit-tests > depends on > domain + domain-test

domain-test > depends on domain

This sounds terribly bad to me to separate code and tests in separated maven module. Maybe I should grow or maybe I should change my tools ...

Solution

Keep everything in the same maven module but create a 'test-jar' with only the DSL classes.

2 things needed

  1. put all the classes you need in independent packages

I will use the "filter" feature of the maven-jar-plugin to export only what I need. The easiest way to do so is to isolate the needed class in packages that only contains only these classes.

  1. create a test-jar :
<plugin>
  <!-- Build a test jar with test classes that other module might use -->
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-jar-plugin</artifactId>
  <version>3.2.0</version>
  <executions>
    <execution>
      <goals>
        <goal>test-jar</goal>
      </goals>
      <configuration>
        <includes>
          <include>com/myapp/testdsl/**</include>
        </includes>
      </configuration>
    </execution>
  </executions>
</plugin>

To use a test-jar, you must add the type tag in the dependency declaration :

<dependency>
  <groupId>com.mycompany</groupId>
  <artifactId>domain</artifactId>
  <version>2.1</version>
  <type>test-jar</type>
  <scope>test</scope>
</dependency>

IntelliJ caveat

I have one caveat with that solution, when using IntelliJ:

The IDE does not take the <include> filter into account and all the classes are in the classpath. This is a known IntelliJ bug (https://youtrack.jetbrains.com/issue/IDEA-134943) unresolved so far

Extenuation answered 27/12, 2019 at 16:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.