Code Monkey home page Code Monkey logo

specnaz's Introduction

Specnaz

License Download Build Status

Library for writing beautiful, RSpec/Jasmine/Mocha/Jest-style specifications in Java, Kotlin and Groovy

Specnaz logo

First example

import org.specnaz.Specnaz;
import org.specnaz.junit.SpecnazJUnit;
import org.specnaz.junit.SpecnazJUnitRunner;
import org.junit.runner.RunWith;
import org.junit.Assert;
import java.util.Stack;

// You can also say:
// public class StackSpec extends SpecnazJUnit {{
// - in that case, you can drop the @RunWith annotation
@RunWith(SpecnazJUnitRunner.class)
public class StackSpec implements Specnaz {{
    describes("A Stack", it -> {
        Stack<Integer> stack = new Stack<>();

        it.should("be empty when first created", () -> {
            Assert.assertTrue(stack.isEmpty());
        });

        it.endsEach(() -> {
            stack.clear();
        });

        it.describes("with 10 and 20 pushed on it", () -> {
            it.beginsEach(() -> {
                stack.push(10);
                stack.push(20);
            });

            it.should("have size equal to 2", () -> {
                Assert.assertEquals(2, stack.size());
            });

            it.should("have 20 as the top element", () -> {
                Assert.assertEquals(20, (int)stack.peek());
            });
        });
    });
}}

This is how the above test looks like when executed from an IDE:

StackSpec result

Notable features

Compatible with existing Java testing frameworks

Specnaz integrates with every major Java testing framework:

This is important for 2 reasons. First, it means Specnaz works out of the box with your existing IDEs and build tools, without requiring the installation of any additional plugins. And second, it means adopting Specnaz is not an all or nothing decision: you can introduce it gradually to your test suite, on a trial basis, and the new Specnaz tests and any existing JUnit or TestNG tests will coexist next to each other seamlessly. Adopting Specnaz does not force you into any sort of migration.

See below for a summary of which Specnaz library to use with each testing framework.

Built-in parametrized test capabilities

Specnaz allows you to easily create concise and type-safe parametrized tests. Example:

import org.specnaz.junit.SpecnazJUnitRunner;
import org.specnaz.params.SpecnazParams;
import org.specnaz.params.junit.SpecnazParamsJUnit;
import org.junit.runner.RunWith;

import static org.specnaz.params.Params3.p3;

// You can also say:
// public class ParametrizedSpec extends SpecnazParamsJUnit {{
// - in that case, you can skip the @RunWith annotation
@RunWith(SpecnazJUnitRunner.class)
public class ParametrizedSpec implements SpecnazParams {{
    describes("A parametrized spec", it -> {
        it.should("confirm that %1 + %2 = %3", (Integer a, Integer b, Integer c) -> {
            assertThat(a + b).isEqualTo(c);
        }).provided(
                p3(1, 2, 3),
                p3(4, 4, 8),
                p3(-3, 3, 0),
                p3(Integer.MAX_VALUE, 1, Integer.MIN_VALUE)
        );
    });
}}

See here for more information on writing parametrized specs.

First-class support for the Kotlin language

Specnaz supports writing specs in idiomatic Kotlin:

import org.specnaz.kotlin.junit.SpecnazKotlinJUnit
import org.junit.Assert
import java.util.Stack

class StackKotlinSpec : SpecnazKotlinJUnit("A Stack", {
    var stack = Stack<Int>()

    it.should("be empty when first created") {
        Assert.assertTrue(stack.isEmpty())
    }

    it.endsEach {
        stack = Stack()
    }

    it.describes("with 10 and 20 pushed on it") {
        it.beginsEach {
            stack.push(10)
            stack.push(20)
        }

        it.should("have size equal to 2") {
            Assert.assertEquals(2, stack.size)
        }

        it.should("have 20 as the top element") {
            Assert.assertEquals(20, stack.peek())
        }
    }
})

See here for more information.

Further reading

Check out the reference manual for more in-depth documentation. There is also an examples directory with code samples.

I've also written a post on my blog demonstrating how you can structure your tests with Specnaz.

Getting Specnaz

Specnaz is available through the Maven Central repository.

  • Group ID: org.specnaz
  • Latest version: 1.5.3

The Artifact ID depends on the language and testing framework you want to use:

Programming language Testing framework Artifact ID
Java / Groovy JUnit 4 specnaz-junit
Kotlin JUnit 4 specnaz-kotlin-junit
Java / Groovy TestNG specnaz-testng
Kotlin TestNG specnaz-kotlin-testng
Java / Groovy JUnit 5 specnaz-junit-platform
Kotlin JUnit 5 specnaz-kotlin-junit-platform

Note: the Specnaz libraries don't depend on their testing frameworks (neither JUnit, nor TestNG), and also not on the Kotlin runtime in the case of the Kotlin libraries. This is in order to prevent version conflicts. Make sure to add the appropriate dependencies on JUnit or TestNG (and the Kotlin runtime if applicable) if your project doesn't include them already.

Example Maven settings
<dependencies>
    <!-- For JUnit 4: -->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
    <!-- ...in Java / Groovy: -->
    <dependency>
        <groupId>org.specnaz</groupId>
        <artifactId>specnaz-junit</artifactId>
        <version>1.5.3</version>
        <scope>test</scope>
    </dependency>
    <!-- ...or Kotlin: -->
    <dependency>
        <groupId>org.specnaz</groupId>
        <artifactId>specnaz-kotlin-junit</artifactId>
        <version>1.5.3</version>
        <scope>test</scope>
    </dependency>

    <!-- For TestNG: -->
    <dependency>
        <groupId>org.testng</groupId>
        <artifactId>testng</artifactId>
        <version>6.14.3</version>
        <scope>test</scope>
    </dependency>
    <!-- ...in Java / Groovy: -->
    <dependency>
        <groupId>org.specnaz</groupId>
        <artifactId>specnaz-testng</artifactId>
        <version>1.5.3</version>
        <scope>test</scope>
    </dependency>
    <!-- ...or Kotlin: -->
    <dependency>
        <groupId>org.specnaz</groupId>
        <artifactId>specnaz-kotlin-testng</artifactId>
        <version>1.5.3</version>
        <scope>test</scope>
    </dependency>

    <!-- For JUnit 5: -->
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter</artifactId>
        <version>5.5.2</version>
        <scope>test</scope>
    </dependency>
    <!-- ...in Java / Groovy: -->
    <dependency>
        <groupId>org.specnaz</groupId>
        <artifactId>specnaz-junit-platform</artifactId>
        <version>1.5.3</version>
        <scope>test</scope>
    </dependency>
    <!-- ...or Kotlin: -->
    <dependency>
        <groupId>org.specnaz</groupId>
        <artifactId>specnaz-kotlin-junit-platform</artifactId>
        <version>1.5.3</version>
        <scope>test</scope>
    </dependency>
</dependencies>
Example Gradle settings
repositories {
    mavenCentral()
}

dependencies {
    // For JUnit 4:
    testCompile "junit:junit:4.12"
    // ...in Java / Groovy:
    testCompile "org.specnaz:specnaz-junit:1.5.3"
    // ...or Kotlin:
    testCompile "org.specnaz:specnaz-kotlin-junit:1.5.3"

    // For TestNG:
    testCompile "org.testng:testng:6.14.3"
    // ...in Java / Groovy:
    testCompile "org.specnaz:specnaz-testng:1.5.3"
    // ...or Kotlin:
    testCompile "org.specnaz:specnaz-kotlin-testng:1.5.3"

    // For JUnit 5:
    testCompile "org.junit.jupiter:junit-jupiter:5.5.2"
    // ...in Java / Groovy:
    testCompile "org.specnaz:specnaz-junit-platform:1.5.3"
    // ...or Kotlin:
    testCompile "org.specnaz:specnaz-kotlin-junit-platform:1.5.3"
}

License

Specnaz is open-source software, released under the Apache v2 license. See the License file for details.

specnaz's People

Contributors

eduardo-ortega102 avatar krzysztof-jelski avatar rlbisbe avatar skinny85 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

specnaz's Issues

Support tests expecting Exceptions

In "regular" JUnit, you can write a test like so:

import org.junit.Test;

import java.util.ArrayList;

public class ExpectTest {
    @Test(expected = IndexOutOfBoundsException.class)
    public void expect_exception_test() throws Exception {
        new ArrayList<String>().get(0);
    }
}

There was a Feature Request to add a similar capability to Specnaz.

Problem with Maven

Hi!

the repository section in your maven example is not working, you need to replace http by https

Have a nice day! :)

Testing multiplatform code?

Hi there!

I was wondering: is specnaz ever going to support testing of platform-agnostic code? As far as I see, now it's coupled with JVM platform (JUnit/TestNG). When one wants to test a platform-agnostic Kotlin code, I guess we're stuck with kotlin.test for now. I'd love to be able to write these tests conveniently with specnaz!

If you don't plan to support it, it would be nice to drop a sentence in the library description that it's solely for JVM.

Cheers!

Specnaz TestNG runner fails, by trying to run *describes lambdas as test (and failing at it)

Examples of errors:

   [testng] 
   [testng] [Utils] [ERROR] [Error] org.testng.TestNGException: 
   [testng] Cannot inject @Test annotated Method [describes] with [class java.lang.String, interface kotlin.jvm.functions.Function1].
   [testng] For more information on native dependency injection please refer to http://testng.org/doc/documentation-main.html#native-dependency-injection
   [testng]     at org.testng.internal.Parameters.checkParameterTypes(Parameters.java:407)
   [testng]     at org.testng.internal.Parameters.createParametersForMethod(Parameters.java:356)
   [testng]     at org.testng.internal.Parameters.createParameters(Parameters.java:635)
   [testng]     at org.testng.internal.Parameters.handleParameters(Parameters.java:769)
   [testng]     at org.testng.internal.ParameterHandler.handleParameters(ParameterHandler.java:49)
   [testng]     at org.testng.internal.ParameterHandler.createParameters(ParameterHandler.java:37)
   [testng]     at org.testng.internal.Invoker.invokeTestMethods(Invoker.java:923)
   [testng]     at org.testng.internal.TestMethodWorker.invokeTestMethods(TestMethodWorker.java:125)
   [testng]     at org.testng.internal.TestMethodWorker.run(TestMethodWorker.java:109)
   [testng]     at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
   [testng]     at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
   [testng]     at java.lang.Thread.run(Thread.java:748)
   [testng] 
   [testng] [Utils] [ERROR] [Error] org.testng.TestNGException: 
   [testng] Cannot inject @Test annotated Method [xdescribes] with [class java.lang.String, interface kotlin.jvm.functions.Function1].
   [testng] For more information on native dependency injection please refer to http://testng.org/doc/documentation-main.html#native-dependency-injection
   [testng]     at org.testng.internal.Parameters.checkParameterTypes(Parameters.java:407)
   [testng]     at org.testng.internal.Parameters.createParametersForMethod(Parameters.java:356)
   [testng]     at org.testng.internal.Parameters.createParameters(Parameters.java:635)
   [testng]     at org.testng.internal.Parameters.handleParameters(Parameters.java:769)
   [testng]     at org.testng.internal.ParameterHandler.handleParameters(ParameterHandler.java:49)
   [testng]     at org.testng.internal.ParameterHandler.createParameters(ParameterHandler.java:37)
   [testng]     at org.testng.internal.Invoker.invokeTestMethods(Invoker.java:923)
   [testng]     at org.testng.internal.TestMethodWorker.invokeTestMethods(TestMethodWorker.java:125)
   [testng]     at org.testng.internal.TestMethodWorker.run(TestMethodWorker.java:109)
   [testng]     at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
   [testng]     at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
   [testng]     at java.lang.Thread.run(Thread.java:748)
   [testng] 
   [testng] PASSED: test should work
   [testng] FAILED: describes
   [testng] org.testng.TestNGException: 
   [testng] Cannot inject @Test annotated Method [describes] with [class java.lang.String, interface kotlin.jvm.functions.Function1].
   [testng] For more information on native dependency injection please refer to http://testng.org/doc/documentation-main.html#native-dependency-injection
   [testng]     at org.testng.internal.Parameters.checkParameterTypes(Parameters.java:407)
   [testng]     at org.testng.internal.Parameters.createParametersForMethod(Parameters.java:356)
   [testng]     at org.testng.internal.Parameters.createParameters(Parameters.java:635)
   [testng]     at org.testng.internal.Parameters.handleParameters(Parameters.java:769)
   [testng]     at org.testng.internal.ParameterHandler.handleParameters(ParameterHandler.java:49)
   [testng]     at org.testng.internal.ParameterHandler.createParameters(ParameterHandler.java:37)
   [testng]     at org.testng.internal.Invoker.invokeTestMethods(Invoker.java:923)
   [testng]     at org.testng.internal.TestMethodWorker.invokeTestMethods(TestMethodWorker.java:125)
   [testng]     at org.testng.internal.TestMethodWorker.run(TestMethodWorker.java:109)
   [testng]     at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
   [testng]     at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
   [testng]     at java.lang.Thread.run(Thread.java:748)
   [testng] 
   [testng] FAILED: xdescribes
   [testng] org.testng.TestNGException: 
   [testng] Cannot inject @Test annotated Method [xdescribes] with [class java.lang.String, interface kotlin.jvm.functions.Function1].
   [testng] For more information on native dependency injection please refer to http://testng.org/doc/documentation-main.html#native-dependency-injection
   [testng]     at org.testng.internal.Parameters.checkParameterTypes(Parameters.java:407)
   [testng]     at org.testng.internal.Parameters.createParametersForMethod(Parameters.java:356)
   [testng]     at org.testng.internal.Parameters.createParameters(Parameters.java:635)
   [testng]     at org.testng.internal.Parameters.handleParameters(Parameters.java:769)
   [testng]     at org.testng.internal.ParameterHandler.handleParameters(ParameterHandler.java:49)
   [testng]     at org.testng.internal.ParameterHandler.createParameters(ParameterHandler.java:37)
   [testng]     at org.testng.internal.Invoker.invokeTestMethods(Invoker.java:923)
   [testng]     at org.testng.internal.TestMethodWorker.invokeTestMethods(TestMethodWorker.java:125)
   [testng]     at org.testng.internal.TestMethodWorker.run(TestMethodWorker.java:109)
   [testng]     at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
   [testng]     at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
   [testng]     at java.lang.Thread.run(Thread.java:748)

From @skinny85 investigation this happens only when you run TestNG, passing it a package name (but it works fine if you pass the class names directly).

The proposed workaround is to add another test listener for TestNG, with the following content:

package specnaz;
 
import org.specnaz.kotlin.SpecnazKotlin;
import org.specnaz.kotlin.params.SpecnazKotlinParams;
import org.testng.IAlterSuiteListener;
import org.testng.xml.XmlClass;
import org.testng.xml.XmlPackage;
import org.testng.xml.XmlSuite;
import org.testng.xml.XmlTest;
 
import java.util.List;
 
import static java.util.stream.Collectors.toList;
 
public class SecondListener implements IAlterSuiteListener {
    @Override
    public void alter(List<XmlSuite> xmlSuites) {
        for (XmlSuite xmlSuite : xmlSuites) {
            alterXmlSuite(xmlSuite);
        }
    }
 
    private void alterXmlSuite(XmlSuite xmlSuite) {
        xmlSuite.setTests(xmlSuite.getTests().stream()
                .map(xmlTest -> alterXmlTest(xmlTest))
                .collect(toList())
        );
    }
 
    private XmlTest alterXmlTest(XmlTest xmlTest) {
        xmlTest.setPackages(xmlTest.getPackages().stream()
                .map(xmlPackage -> alterXmlPackage(xmlPackage))
                .collect(toList())
        );
        return xmlTest;
    }
 
    private XmlPackage alterXmlPackage(XmlPackage xmlPackage) {
        xmlPackage.getXmlClasses().forEach(xmlClass -> alterXmlClass(xmlClass));
        return xmlPackage;
    }
 
    private XmlClass alterXmlClass(XmlClass xmlClass) {
        if (isSpecnazKotlinClass(xmlClass))
            xmlClass.getExcludedMethods().add(".*describes");
        return xmlClass;
    }
 
    private boolean isSpecnazKotlinClass(XmlClass xmlClass) {
        return SpecnazKotlin.class.isAssignableFrom(xmlClass.getSupportClass()) ||
                SpecnazKotlinParams.class.isAssignableFrom(xmlClass.getSupportClass());
    }
}

Support for top-level `fdescribes`?

Hello there, and thanks for specnaz!

While trying to write a few examples to get acquainted with the library, I noticed that fdescribes is only defined on SpecBuilder but not on Specnaz.

This behavior was quite unexpected to me, as I had several fdescribes that worked, but one that behaved differently -- the top-level one.

Would you consider adding it? Thanks ๐Ÿ™

Fail on no tests found?

In specnaz the following code results in "no test found" and a sucesful build.

import org.specnaz.junit.SpecnazJUnit;

public class TestSpec extends SpecnazJUnit {
    {
        describes("Test spec", it -> {
        });
    }
}

This is the IntelliJ output:

Process finished with exit code 0
Empty test suite.

If we run it as part of a bigger test set, the result is similar.

Process finished with exit code 0

Should we make the test fail if the discovery phase results in zero tests?

For reference, if you add a test in JUnit and you mark it as ignored, it's effectively the same.

public class TestTest {

    @Test
    @Ignore
    public void doTest(){

    }
}

The output for this method is the following.

Test ignored.
Process finished with exit code 0

Better Mockito integration

In "regular" JUnit, you can use the MockitoJUnitRunner to avoid explicitly initializing mocks (among other things - see the JavaDocs linked above for details):

 @RunWith(MockitoJUnitRunner.class)
 public class ExampleTest {

     @Mock
     private List list;

     @Test
     public void shouldDoSomething() {
         list.add(100);
     }
 }

There was a Feature Request to add this capability to Specnaz.

Top-level description missing from terminal output when running tests

When running Specnaz tests from the console, the top-level description (the one given to the describes method from the Specnaz interface) is not always shown when reporting the name of the tests.

This depends somewhat on the build tool being used, but, for Gradle (if you add:

test {
    testLogging {
        events "passed", "skipped", "failed"
    }
}

to your build.gradle), for the following, slightly modified StackSpec from the Readme.md:

import org.junit.Assert;
import org.specnaz.junit.SpecnazJUnit;

import java.util.Stack;

public class StackSpec extends SpecnazJUnit {{
    describes("A Stack", it -> {
        Stack<Integer> stack = new Stack<>();

        it.should("be empty when first created", () -> {
            Assert.assertTrue(stack.isEmpty());
        });

        it.endsEach(() -> {
            stack.clear();
        });

        it.describes("with 10 and 20 pushed on it", () -> {
            it.beginsEach(() -> {
                stack.push(10);
                stack.push(20);
            });

            it.should("have size equal to 2", () -> {
                Assert.assertEquals(2, stack.size());
            });

            it.should("have 20 as the top element", () -> {
                Assert.assertEquals(20, (int)stack.peek());
            });

            it.describes("and then the top element popped", () -> {
                it.beginsEach(() -> {
                    stack.pop();
                });

                it.should("have size 1", () -> {
                    Assert.assertEquals(1, stack.size());
                });
            });
        });
    });
}}

...the Gradle output is the following:

StackSpec > A Stack.should be empty when first created PASSED
StackSpec > with 10 and 20 pushed on it.should have size equal to 2 PASSED
StackSpec > with 10 and 20 pushed on it.should have 20 as the top element PASSED
StackSpec > and then the top element popped.should have size 1 PASSED

A correct output would probably look more like:

StackSpec.A Stack > should be empty when first created PASSED
StackSpec.A Stack > with 10 and 20 pushed on it.should have size equal to 2 PASSED
StackSpec.A Stack > with 10 and 20 pushed on it.should have 20 as the top element PASSED
StackSpec.A Stack > and then the top element popped.should have size 1 PASSED

formatParamsDesc not sanitizing input

Specnaz Version 1.5.2

It looks like Conversions.java, line 344 needs to sanitize the replacement string.

Specifically, it looks like a $ in the replacement string is a problem

class FormatTests: SpecnazKotlinParamsJUnitPlatform("Format", {
    it.describes("failure %1") { _: Any ->
        it.should("not fail") {
        }
    }.provided(object {}, "Nope$")
})

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.