Jesse
Jesse

Reputation: 273

@BeforeGroups method ignores dependsOnGroups

I have a class with several tests split into two groups. I want a strict ordering to when the grouped tests are run, such that the tests in group A run first, then a setup method for group B is executed, and then group B runs. For example:

@Test(groups="A")
public void a1() {
    // ...
}

@Test(groups="A")
public void a2() {
    // ...
}

@BeforeGroups(value="B", dependsOnGroups="A")
public void setupB() {
    // ...
}

@Test(groups="B")
public void b1() {
    // ...
}

@Test(groups="B")
public void b2() {
    // ...
}

The problem I'm running into is that TestNG doesn't seem to be honoring the setupB method. Instead of the expected execution order:
a1/a2
a2/a1
setupB
b1/b2
b2/b1

It executes something like this:
a1
setupB
b1
a2
b2

Any idea what I'm doing wrong with this setup? Am I missing something conceptually about how TestNG's groups work?

Upvotes: 4

Views: 407

Answers (3)

Roman Kishchenko
Roman Kishchenko

Reputation: 677

As mentioned earlier, when a test method is invoked, it just checks whether the corresponding @BeforeGroups annotated method has been executed for the test group the method belongs to, and if it hasn't, TestNG just invokes the @BeforeGroups method.

The workaround is to add a custom IMethodInterceptor which would add dependencies on the groups from @BeforeGroups#dependsOnGroups for the test methods, related to the same group:

public class TestMethodInterceptor implements IMethodInterceptor {

    @Override
    public List<IMethodInstance> intercept(List<IMethodInstance> methods, ITestContext context) {
        if (!(context instanceof TestRunner)) {
            return methods;
        }

        TestRunner testRunner = (TestRunner) context;
        Collection<ITestClass> testClasses = testRunner.getTestClasses();
        Map<String, List<String>> groupDependencies = MethodGroupsHelper.findGroupsMethods(testClasses, true).entrySet().stream()
                .collect(Collectors.toMap(
                        Map.Entry::getKey,
                        groupBeforeMethods -> groupBeforeMethods.getValue().stream()
                                .flatMap(m -> Arrays.stream(m.getGroupsDependedUpon()))
                                .collect(Collectors.toList())
                ));

        return methods.stream()
                .map(IMethodInstance::getMethod)
                .map(method -> {
                    Set<String> methodGroupDependencies = Stream.ofNullable(method.getGroups())
                            .flatMap(Arrays::stream)
                            .flatMap(group -> groupDependencies.getOrDefault(group, List.of()).stream())
                            .collect(Collectors.toSet());
                    return methodGroupDependencies.isEmpty() ? method : addGroupDependencies(method, methodGroupDependencies);
                })
                .map(MethodInstance::new)
                .collect(Collectors.toList());

    }

    private ITestNGMethod addGroupDependencies(ITestNGMethod method, Collection<String> groups) {
        String[] methodGroupDependencies;
        if (method.getGroupsDependedUpon() == null || method.getGroupsDependedUpon().length == 0) {
            methodGroupDependencies = groups.toArray(new String[0]);
        } else {
            methodGroupDependencies = Stream.concat(Arrays.stream(method.getGroupsDependedUpon()), groups.stream())
                    .distinct()
                    .toArray(String[]::new);
        }

        return new WrappedTestNGMethod(method) {
            @Override
            public String[] getGroupsDependedUpon() {
                return methodGroupDependencies;
            }
        };
    }

}

The TestMethodInterceptor is a listener that can be added to the execution by means of @Listeners annotation.

Upvotes: 0

Shamik
Shamik

Reputation: 1609

This might be a workaround for now if it works. Not sure if you can use both annotations together.

@Test(groups="A")

public void a1() {
    // ...
}

@Test(groups="A")
public void a2() {
    // ...
}

@BeforeGroups(value="B")
@AfterGroups(value="A")
public void setupB() {
    // ...
}

@Test(groups="B")
public void b1() {
    // ...
}

@Test(groups="B")
public void b2() {
    // ..

. }

Upvotes: 0

Constantine
Constantine

Reputation: 3257

Try to specify dependsOnGroups for test methods as well.

public class TestClass {

    @Test(groups="B")
    public void b1() {
        System.out.println("b1");
    }

    @Test(groups="B")
    public void b2() {
        System.out.println("b2");
    }

    @Test(groups="A", dependsOnGroups="B")
    public void a1() {
        System.out.println("a1");
    }

    @Test(groups="A", dependsOnGroups="B")
    public void a2() {
        System.out.println("a2");
    }

    @BeforeGroups(value="A", dependsOnGroups="B")
    public void setupA() {
        System.out.println("before");
    }
}

I may be wrong about it, but seems that if a test method that belongs to a group has been picked for execution and it does not depend on any groups or methods, it just causes @BeforeGroups-annotated method to be run (ignoring dependsOnGroups specified there). Note that TestNG does not guarantee the execution order without some explicit declaration, e.g. using "depends" or "priority" mechanisms.

Hopefully, Cedric Beust will pay this question a visit.

Upvotes: 2

Related Questions