Snapshot Testing framework for Kotlin.

Overview

Karumi logo KotlinSnapshot Build Status

Snapshot Testing framework for Kotlin.

What is this?

Snapshot testing is an assertion strategy based on the comparision of the instances serialized using a human readable format we version as part of the repository source code.

Getting started

Add our Gradle Plugin to your build.gradle file:

buildscript {
  repositories {
    mavenCentral()
  }
  dependencies {
    classpath 'com.karumi.kotlinsnapshot:plugin:2.2.3'
  }
}

apply plugin: 'com.karumi.kotlin-snapshot'

Invoke the extension function named matchWithSnapshot from any instance. The name of the snapshot is not mandatory, if you don't specify it as the first matchWithSnapshot param the library will infer it from the test execution context. Example:

package com.mypackage

class NetworkTest {

    private val networkClient = MyNetworkClient()

    @Test
    fun shouldFetchDataFromNetwork() {
        val myData = networkClient.fetchData()
        myData.matchWithSnapshot()
    }

    @Test
    fun shouldFetchDataFromNetworkWithSpecificSnapshotName() {
        val myData = networkClient.fetchData()
        myData.matchWithSnapshot("should fetch the data from the network")
    }

If you need to customize the snapshots folder path you can create an instance of KotlinSnapshot in your test file and use the method matchWithSnapshot, which takes 2 arguments: A string with the name of the snapshot and an Any object to be saved using its json serialized version using a customized version of GSON.

val kotlinSnapshot = KotlinSnapshot(relativePath = "src/test/kotlin/com/my/package")

After you run the test for the first time, a new snapshot will be written in the __snapshot__ directory of the root of your project. The written snapshot for this example would look like this:

$ cat __snapshot__/should\ fetch\ data\ from\ network.snap 
{"name":"gabriel","id":5}

You can also configure KotlinSnapshot to group every snapshot file into a directory named using the test class name:

val kotlinSnapshot = KotlinSnapshot(relativePath = "src/test/kotlin/com/my/package", testClassAsDirectory = true)

The snapshot will be generated inside a directory with the name of the test instead of putting it in __snapshot__ folder. In the previous example, the test will be created inside com.mypackage.NetworkTest:

$ cat __snapshot__/my.package.NetworkTest/should\ fetch\ data\ from\ network.snap 
{"name":"gabriel","id":5}

On subsequent runs, the value will be compared with the snapshot stored in the filesystem if they are not equal, your test will fail. To see the detailed error you may need to run your tests with ./gradlew test --info. You should see something like this:

Snapshot Error

Updating Snapshots

In order to update snapshots from the command line, you just need to execute one command:

./gradlew updateSnapshots

or, for newer versions of Gradle:

updateSnapshots=1 ./gradlew test

Purging Snapshots

As you rename snapshots, old unused snapshots may remain in your project. You can delete all existing snapshots and rebuild the ones that are actually used using the "purgeSnapshots" gradle task

./gradlew purgeSnapshots

or, for newer versions of Gradle:

purgeSnapshots=1 ./gradlew test

Contributing

Linting and formatting

This repository uses ktlint. This Gradle plugin ensures the code style is homogeneous and always correct thanks to the evaluation of the code during the build execution. You can use these commands in order to check if the code changes passes the repository codestyle and to format the code automatically:

./gradlew ktlint //Checks if the project passes the checkstyle.
./gradlew ktlintFormat //Formats the code for you

Running tests

This project contains some tests written using JUnit. You can easily run the tests by executing the following commands:

./gradlew test //Run every test.
./gradlew test -t //Run every test using the watch mode.
./gradlew test --tests "com.xyz.b.module.TestClass.testToRun" //Run a single test

Customizing serializations

KotlinSnapshot uses custom serialization for the basic types. If, for some reason, you want to implement your custom serializer you can create your own SerializationModule. If you still want to reuse part of the serialization we provide you can compose your serializer as follows:

class CustomKotlinSerialization : SerializationModule {

        private val kotlinSerialization = KotlinSerialization()

        override fun serialize(value: Any?): String = when {
            value is LocalDate -> "custom serialization configured"
            else -> kotlinSerialization.serialize(value)
        }
}

Take into account that the KotlinSerialization class uses Gson under the hood. This class transforms your instances into json strings you can easily review when needed. On top of the json serialization we add some metadata really useful when serializing sealed hierarchies or objects. If for some reason you need to extend the serializer and use your own custom serializer also based on Gson you can do it as follows:

class CustomKotlinJsonSerialization: SerializationModule {

        private val customGson = KotlinSerialization.gsonBuilder
            .setDateFormat("yyyy-MM-dd")
            .create()

        override fun serialize(value: Any?): String = customGson.toJson(value)
}

Changelog

2.2.0 Improve diff coloring:

  • User more conventional diff coloring
  • Support window line endings replacing '\r\n' to '\n'

2.1.1 Fixed JUnit 5 support:

2.1.0 Improve IntelliJ and JUnit 5 support:

  • Support for nullable types.
  • Improved IntelliJ Support.
  • Add JUnit 5 Support

2.0.0 Improve the serialization format:

  • We've replaced the old serialization format with a custom JSON format letting the user review the snapshots easily and unify the format. If you update the library to a 2.X version or greater you'll have to record all your tests again.

Sending your PR

If you would like to contribute code to this repository you can do so through GitHub by creating a new branch in the repository and sending a pull request or opening an issue. Please, remember that there are some requirements you have to pass before accepting your contribution:

  • Write clean code and test it.
  • The code written will have to match the product owner requirements.
  • Follow the repository code style.
  • Write good commit messages.
  • Do not send pull requests without checking if the project build is OK in the CI environment.
  • Review if your changes affects the repository documentation and update it.
  • Describe the PR content and don't hesitate to add comments to explain us why you've added or changed something.

License

Copyright 2018 Karumi

Permission is hereby granted, free of charge, to any person 
obtaining a copy of this software and associated documentation 
files (the "Software"), to deal in the Software without restriction, 
including without limitation the rights to use, copy, modify, merge, 
publish, distribute, sublicense, and/or sell copies of the Software, 
and to permit persons to whom the Software is furnished to do so, 
subject to the following conditions:

The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
Comments
  • 70 fail on missing snapshot

    70 fail on missing snapshot

    :pushpin: References

    • Issue: #70

    :tophat: What is the goal?

    Allow to run KotlinSnapshot in a mode where it fails on missing snapshots.

    :memo: How is it being implemented?

    • I have introduced a new property/env variable failOnMissingSnapshots. If this is set to 1, the camera will fail instead of writing the snapshot.

    :boom: How can it be tested?

    • Write a snapshot test with missing snapshot, run failOnMissingSnapshots=1 ./gradlew test – The test should fail.
    • Run ./gradlew test – The snapshot should be created and the test should pass
    • Run failOnMissingSnapshots=1 ./gradlew test – The test still should pass.

    See also: CameraTest.

    opened by TobiasMende 16
  • Add support for Android UI tests

    Add support for Android UI tests

    :pushpin: References

    • Issue: https://github.com/Karumi/KotlinSnapshot/issues/57

    :tophat: What is the goal?

    Add support to Android UI Tests

    :memo: How is it being implemented?

    Automatically add project dependency for androidTestImplementation for this plugin

    :boom: How can it be tested?

    Create a new test under androidTest and reference KotlinSnapshot

    If it cannot be tested explain why.

    • [ ] Use case 1: Release a new SNAPSHOT version of the library and pull it into an project that has androidTest source
    opened by changusmc 15
  • WIP Improve serialization format

    WIP Improve serialization format

    :pushpin: References

    • Issue: #42

    :tophat: What is the goal?

    Improve our custom serialization format by adding some random spaces. I'm sending this PR so we have something to discuss. I'd like to resolve the format and serialization issue but this PR is not the way to go. If I were you I'd not merge this branch

    How is it being implemented?

    We've added some spaces based on the depth level and thanks to that we can now build trees improving the format. The solution is not ideal at all because our format doesn't let us tokenize easily.

    At this point, I'm thinking the best solution is to move to Gson and create a custom serialization process to transform any instance into a String and then use any library to pretty print the result. I know we decided to use toString method because of the first implementation of the library and because we don't need to deserialize any value, but using json could be really interesting. What do you think guys?

    How can it be tested?

    🤖

    opened by pedrovgs 9
  • Bugfix for updating snapshots in newer versions of Gradle

    Bugfix for updating snapshots in newer versions of Gradle

    :pushpin: References

    • Issue: Fixes #62

    :tophat: What is the goal?

    Restore the ability to update snapshots in newer versions of Gradle

    :memo: How is it being implemented?

    Using system environment variables

    :boom: How can it be tested?

    I've added a step to the CI to test this at the integration level. This involves an additional consumer (SpringConsumer) that contains a test that always fails the snapshot.

    If this functionality doesn't work, this test has no way of passing.

    Other Info

    Installed super charger on CI configuration. The pipeline can now run in about two minutes faster than before. Hopefully this gets faster as the cache is built out in TravisCI.

    opened by lcombs15 7
  • CI failing due to toString serialization

    CI failing due to toString serialization

    Running a test like this one locally, the test passes without any problem:

    @Test
    fun showConversationsOnSuccessfulLoading() {
            givenConversationsOnBothDataSources(anyIndividualConversations())
    
            viewModel.viewState.observeForever(viewStateObserver)
    
            viewModel.viewState.value.matchWithSnapshot()
    }
    

    When you run it on CI (CircleCI) it starts failing. You can probably use any other CI systems.

    Expected behaviour

    • Test should also pass.

    Actual behaviour

    Test fails with the following stacktrace (click to expand)

    Salida
    com.karumi.kotlinsnapshot.core.SnapshotException: ?[31mReceived value?[0m does not match ?[32mstored snapshot: "Conversations loaded.snap"?[0m
    ?[32m-Snapshot?[0m
    ?[31m+Received?[0m
    ConversationsViewState(conversations={
    ?[32m- jueves, ene
    ?[0m?[31m+ Thursday, Jan
    ?[0m 1=[IndividualConversation(agentJoinTimes={}, agents=[Author(bio=Any bio text, firstName=Any name, lastName=Any lastname, location=Any location, photoUrl=https://anyphotoservice.com/anyphoto, userId=Any id), Author(bio=Any bio text, firstName=Any name, lastName=Any lastname, location=Any location, photoUrl=https://anyphotoservice.com/anyphoto, userId=Any id), Author(bio=Any bio text, firstName=Any name, lastName=Any lastname, location=Any location, photoUrl=https://anyphotoservice.com/anyphoto, userId=Any id)], conversationId=clientConversation1, mostRecentElement=Left(a=Message(author=Author(bio=null, firstName=Client, lastName=www.client.com/photoUrl, location=null, photoUrl=null, userId=Client), conversationId=correlationId, date=Date(, ), direction=OUTGOING, messageId=Conversation1, readStatus=Read(readTime=Date(, )), status=PENDING_SEND, text=Text1), , ), status=Open, unreadCount=0), IndividualConversation(agentJoinTimes={}, agents=[Author(bio=Any bio text, firstName=Any name, lastName=Any lastname, location=Any location, photoUrl=https://anyphotoservice.com/anyphoto, userId=Any id), Author(bio=Any bio text, firstName=Any name, lastName=Any lastname, location=Any location, photoUrl=https://anyphotoservice.com/anyphoto, userId=Any id), Author(bio=Any bio text, firstName=Any name, lastName=Any lastname, location=Any location, photoUrl=https://anyphotoservice.com/anyphoto, userId=Any id)], conversationId=clientConversation2, mostRecentElement=Left(a=Message(author=Author(bio=null, firstName=Client, lastName=www.client.com/photoUrl, location=null, photoUrl=null, userId=Client), conversationId=correlationId, date=Date(, ), direction=OUTGOING, messageId=Conversation1, readStatus=Read(readTime=Date(, )), status=PENDING_SEND, text=Text2), , ), status=Open, unreadCount=0), IndividualConversation(agentJoinTimes={}, agents=[Author(bio=Any bio text, firstName=Any name, lastName=Any lastname, location=Any location, photoUrl=https://anyphotoservice.com/anyphoto, userId=Any id), Author(bio=Any bio text, firstName=Any name, lastName=Any lastname, location=Any location, photoUrl=https://anyphotoservice.com/anyphoto, userId=Any id), Author(bio=Any bio text, firstName=Any name, lastName=Any lastname, location=Any location, photoUrl=https://anyphotoservice.com/anyphoto, userId=Any id)], conversationId=clientConversation3, mostRecentElement=Left(a=Message(author=Author(bio=null, firstName=Client, lastName=www.client.com/photoUrl, location=null, photoUrl=null, userId=Client), conversationId=correlationId, date=Date(, ), direction=OUTGOING, messageId=Conversation1, readStatus=Read(readTime=Date(, )), status=PENDING_SEND, text=Text3), , ), status=Open, unreadCount=0), IndividualConversation(agentJoinTimes={}, agents=[Author(bio=Any bio text, firstName=Any name, lastName=Any lastname, location=Any location, photoUrl=https://anyphotoservice.com/anyphoto, userId=Any id), Author(bio=Any bio text, firstName=Any name, lastName=Any lastname, location=Any location, photoUrl=https://anyphotoservice.com/anyphoto, userId=Any id), Author(bio=Any bio text, firstName=Any name, lastName=Any lastname, location=Any location, photoUrl=https://anyphotoservice.com/anyphoto, userId=Any id)], conversationId=clientConversation4, mostRecentElement=Left(a=Message(author=Author(bio=null, firstName=Client, lastName=www.client.com/photoUrl, location=null, photoUrl=null, userId=Client), conversationId=correlationId, date=Date(, ), direction=OUTGOING, messageId=Conversation1, readStatus=Read(readTime=Date(, )), status=PENDING_SEND, text=Text4), , ), status=Open, unreadCount=0), IndividualConversation(agentJoinTimes={}, agents=[Author(bio=Any bio text, firstName=Any name, lastName=Any lastname, location=Any location, photoUrl=https://anyphotoservice.com/anyphoto, userId=Any id), Author(bio=Any bio text, firstName=Any name, lastName=Any lastname, location=Any location, photoUrl=https://anyphotoservice.com/anyphoto, userId=Any id), Author(bio=Any bio text, firstName=Any name, lastName=Any lastname, location=Any location, photoUrl=https://anyphotoservice.com/anyphoto, userId=Any id)], conversationId=clientConversation5, mostRecentElement=Left(a=Message(author=Author(bio=null, firstName=Client, lastName=www.client.com/photoUrl, location=null, photoUrl=null, userId=Client), conversationId=correlationId, date=Date(, ), direction=OUTGOING, messageId=Conversation1, readStatus=Read(readTime=Date(, )), status=PENDING_SEND, text=Text5), , ), status=Open, unreadCount=0)]}, showingError=false)
    at com.karumi.kotlinsnapshot.core.Camera.matchValueWithExistingSnapshot(Camera.kt:48)
    at com.karumi.kotlinsnapshot.core.Camera.matchWithSnapshot(Camera.kt:27)
    at com.karumi.kotlinsnapshot.KotlinSnapshotKt.matchWithSnapshot(KotlinSnapshot.kt:28)
    at com.banno.conversations.conversations.ConversationsEndToEndShould.showConversationsOnSuccessfulLoading(ConversationsEndToEndShould.kt:96)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.rules.TestWatcher$1.evaluate(TestWatcher.java:55)
    at org.junit.rules.RunRules.evaluate(RunRules.java:20)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.mockito.internal.runners.DefaultInternalRunner$1.run(DefaultInternalRunner.java:79)
    at org.mockito.internal.runners.DefaultInternalRunner.run(DefaultInternalRunner.java:85)
    at org.mockito.internal.runners.StrictRunner.run(StrictRunner.java:39)
    at org.mockito.junit.MockitoJUnitRunner.run(MockitoJUnitRunner.java:163)
    at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecutor.runTestClass(JUnitTestClassExecutor.java:116)
    at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecutor.execute(JUnitTestClassExecutor.java:59)
    at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecutor.execute(JUnitTestClassExecutor.java:39)
    at org.gradle.api.internal.tasks.testing.junit.AbstractJUnitTestClassProcessor.processTestClass(AbstractJUnitTestClassProcessor.java:66)
    at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.processTestClass(SuiteTestClassProcessor.java:51)
    at sun.reflect.GeneratedMethodAccessor41.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:35)
    at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
    at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:32)
    at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:93)
    at com.sun.proxy.$Proxy1.processTestClass(Unknown Source)
    at org.gradle.api.internal.tasks.testing.worker.TestWorker.processTestClass(TestWorker.java:109)
    at sun.reflect.GeneratedMethodAccessor40.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:35)
    at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
    at org.gradle.internal.remote.internal.hub.MessageHubBackedObjectConnection$DispatchWrapper.dispatch(MessageHubBackedObjectConnection.java:146)
    at org.gradle.internal.remote.internal.hub.MessageHubBackedObjectConnection$DispatchWrapper.dispatch(MessageHubBackedObjectConnection.java:128)
    at org.gradle.internal.remote.internal.hub.MessageHub$Handler.run(MessageHub.java:404)
    at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:63)
    at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:46)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    at org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:55)
    at java.lang.Thread.run(Thread.java:748)
    

    If we remove the noise, the important part would be:

    - jueves, ene
    + Thursday, Jan
    

    The actual date is just a mock date in a nested depth into the object hierarchy, and it is created like Date(1L). It looks like it's correct but its being compared internally by the library using its pretty printed string representation in two different languages (localization). So we can discard this being a usual date / timezone problem.

    If you use a standard approach on your test you would probably assert by using assertEquals or eq (nested equals), and it would pass without any problem, since equals compares the timestamps from both date objects (long). (The test was passing before integrating the lib). It's also significative that the test passes locally, probably because the locale being used is the one expected.

    Digging a bit into the library code, I found out that you're serializing stuff just by calling toString() in a nested way in the object hierarchy. toString() doesn't look like the safest approach for serialization since each type implements it in different ways.

    Regardless of using Kotlin and data classes (with their default toString over all its properties), this is a risky approach. Some types like Date() have an implementation of toString() that relies on things like the machine's Locale. Date#toString() prints a date following the format: "EEE MMM dd HH:mm:ss zzz yyyy" so it'll print a different value depending on the Locale.

    I.e: for date serialization it would work better to serialize them as Long timestamps, which will always match regardless of the timezone.

    I would suggest you to use any existing java serialization libraries to json, protobuf or any other formats that are already covering all those issues, since you probably don't want to end up reinventing the serialization wheel for all the existent types. You can include them as implementation so it's not exposed as a transitive dependency to client projects.

    An interesting question to think about here would be: why was it serialized and stored in the snapshot using one language but then compared after deserialization in a later run using a different one? I have no clue, but both things were done by the same CI environment.

    Steps to reproduce

    • Run this test on a CI environment, I think it should fail.

    Version of the library

    0.3.0

    opened by JorgeCastilloPrz 7
  • Unable to update or purge snapshots in recent version of Gradle

    Unable to update or purge snapshots in recent version of Gradle

    Expected behavior

    ./gradlew updateSnapshots updates snapshots for failing tests

    Actual behavior

    ./gradlew updateSnapshots says tests are failing and no action is taken

    Steps to reproduce

    I'm using the library inside of a Junit 5, Spring @WebMvcTest() test.

    I had a test failing due to a snapshot.

    Running updateSnapshots only tells me the test is failing. No action is taken.

    Version of the library

    2.2.0

    bug help wanted 
    opened by lcombs15 6
  • KotlinSnapshot doesn't extract the test names automatically with JUnit 5

    KotlinSnapshot doesn't extract the test names automatically with JUnit 5

    Expected behaviour

    • KotlinSnapshot should get the test name automatically when you don't set it.

    Actual behaviour

    com.karumi.kotlinsnapshot.exceptions.TestNameNotFoundException: Kotlin Snapshot library couldn't find the name of the test. Review if the test case file or the spec file contains the word test or spec or specify a snapshot name manually, this is a requirement needed to use Kotlin Snapshot
    
    	at com.karumi.kotlinsnapshot.core.Camera.extractTestCaseName(Camera.kt:105)
    	at com.karumi.kotlinsnapshot.core.Camera.matchWithSnapshot(Camera.kt:26)
    	at com.karumi.kotlinsnapshot.KotlinSnapshotKt.matchWithSnapshot(KotlinSnapshot.kt:39)
    	at com.karumi.springbootkotlin.ExtensionsKt.matchWithSnapshot(extensions.kt:52)
    
    

    Steps to reproduce

    • Use JUnit 5 and disable JUnit 4

    Version of the library

    • 2.0.0
    enhancement help wanted 
    opened by tonilopezmr 5
  • Json based serialization format

    Json based serialization format

    :pushpin: References

    • Issue: Fixes #42, fixes #43.

    :tophat: What is the goal?

    Improve our custom serialization format by adding some random spaces. I'm sending this PR so we have something to discuss and compare with the other PR I sent time ago #44. I'd like to resolve the format and serialization issue with this PR and close the other one.

    Thanks to this PR the format we've got is familiar to our users and also can be pretty printed 😃

    How is it being implemented?

    Based on the conversation you can find at #44 I've replaced the default implementation with a serialization format based on Gson library where we add the class name as part of the serialization so we can know if we are serializing an object or a data class with the concrete type. This last scenario is really useful when modeling error with sealed classes.

    We've also added a custom serializer for java dates using always the same timezone so we can serialize every date in the same format even if the code is running on a different time zone.

    I've also updated the documentation and added a changelog for the next tentative major release I'd like to publish with this breaking change.

    Disclaimer: I adapted the java class named RuntimeClassNameTypeAdapterFactory to get it working with our code a and added a link to the original source at the javadoc header.

    The new serialization format changes our simple implementation:

    Developer(name=Sergio, yearsInTheCompany=2)
    

    into this:

    {
      "class": "Developer",
      "name": "Toni",
      "yearsInTheCompany": 1
    } 
    

    This is another example where a complex map is serialized. Before this PR:

    {mobile=[Developer(githubStars=3, name=gabriel), Developer(githubStars=3, name=andres), Developer(githubStars=3, name=miguel)], product=[User(id=1, name=gabriel)], sales=[User(id=1, name=ramon)]}
    

    after this PR:

    {
      "class": "LinkedHashMap",
      "product": [
        {
          "class": "User",
          "id": 1,
          "name": "gabriel"
        }
      ],
      "sales": [
        {
          "class": "User",
          "id": 1,
          "name": "ramon"
        }
      ],
      "mobile": [
        {
          "class": "Developer",
          "name": "gabriel",
          "githubStars": 3
        },
        {
          "class": "Developer",
          "name": "andres",
          "githubStars": 3
        },
        {
          "class": "Developer",
          "name": "miguel",
          "githubStars": 3
        }
      ]
    } 
    

    The class json key is the class simple name associated to the instance we are serializing.

    About the dates serialization, take a look at the difference between a java.util.Date and a LocalDate. The format is different but both are valid, do you like it? Whould you like to use a similar format based on the ISO 8601 format for every date? I'd need to hear your opinion in this scenario.

    Another quiestion I have for you is. Do you like the key class for the object metadata? Would you prefer to use type or any other key?

    How can it be tested?

    🤖 I've added the tests I created for the old PR with some maps and lists nested instances and recorded every test again.

    opened by pedrovgs 4
  • Inferring tests for different suite names

    Inferring tests for different suite names

    First of all, thank you very much for this super cool lib, really good work as always :). Karumi stays strong 💪 :P.

    I got the following test:

    @Test
    fun showConversationsOnSuccessfulLoading() {
            givenConversationsOnBothDataSources(anyIndividualConversations())
    
            viewModel.viewState.observeForever(viewStateObserver)
    
            viewModel.viewState.value.matchWithSnapshot()
    }
    

    Expected behaviour

    Test should pass and store the snapshot on its first run.

    Actual behaviour

    I'm getting the following error (which is expected and well described).

    Kotlin Snapshot library couldn't find the name of the test. Review if the test case file or the spec file contains the word test or spec or specify a snapshot name manually, this is a requirement needed to use Kotlin Snapshot.
    

    This is due to my test suite being called ConversationsEndToEndShould. So the explicit error message was helpful and accurate. I've fixed it by naming the snapshot, like: viewModel.viewState.value.matchWithSnapshot("Conversations loaded")

    I've not digged yet on how you've implemented the test indexing but It'd be nice if it wasn't depending on the test or test class names that much (in case it's possible), or alternatively add a rule for the "should" substring.

    "Should" is kind of used in tests broadly since it gives a good naming when you run them, like: captura de pantalla 2018-09-04 a las 12 33 12

    That's used more often in java probably, since Kotlin already supports using human readable names with spaces and so on in tests, but still it'd be a nice to have.

    Version of the library

    0.3.0

    enhancement help wanted 
    opened by JorgeCastilloPrz 3
  • ./gradlew purgeSnapshots doesn't seem to work

    ./gradlew purgeSnapshots doesn't seem to work

    Expected behaviour

    ./gradlew purgeSnapshots should update the snapshot

    Actual behaviour

    The snapshot test runs and fails without updating the snapshot. In the end I followed the instructions from https://github.com/GAumala/KotlinSnapshot which luckily still works.

    Steps to reproduce

    Execute ./gradlew purgeSnapshots

    Version of the library

    2.2.0

    bug help wanted 
    opened by limsim 2
  • Fix JUnit vintage tests and add vintage engine

    Fix JUnit vintage tests and add vintage engine

    :pushpin: References

    • Related pull-requests: #52 #54

    :tophat: What is the goal?

    • Run JUnit 5 and JUnit Vintage (JUnit4) tests
    • Fix JUnit4 test didn't update in #54

    :memo: How is it being implemented?

    • Upgrade gradlew version
    • Replaced compile to implementation method
    • Add JUnit Vintage engine which is responsible to run find and run JUnit 4 tests, this error was introduced in the last commit of the PR #52

    :boom: How can it be tested?

    • Now in Travis runs all JUnit tests

    I'd like to improve the Travis output because I can't see the test output easily

    opened by tonilopezmr 2
  • [Request] Compatibility with Kotest's StringSpec

    [Request] Compatibility with Kotest's StringSpec

    Expected behaviour

    Either of the cases below produces a snapshot:

    class SomeTest : StringSpec({
      "Base Test" {
        val value = 1
        value.matchWithSnapshot()
    
        val kotlinSnapshot =
          KotlinSnapshot(snapshotsFolder = "src/test/kotlin/com/test", testClassAsDirectory = true)
        kotlinSnapshot.matchWithSnapshot(value)
      }
    })
    

    Actual behaviour

    Kotlin Snapshot library couldn't find the name of the test. Review if the test case file or the spec file contains the word test or spec or specify a snapshot name manually, this is a requirement needed to use Kotlin Snapshot
    com.karumi.kotlinsnapshot.exceptions.TestNameNotFoundException: Kotlin Snapshot library couldn't find the name of the test. Review if the test case file or the spec file contains the word test or spec or specify a snapshot name manually, this is a requirement needed to use Kotlin Snapshot
    	at com.karumi.kotlinsnapshot.core.Camera.extractTestCaseName(Camera.kt:120)
    

    Steps to reproduce

    1. Build a project with Kotest
      val kotestVersion = "4.4.3"
      testImplementation("io.kotest:kotest-runner-junit5-jvm:$kotestVersion")
    
    1. Create a test as above
    2. Run the test

    Version of the library

    2.3.0

    Gradle version

    7.X

    opened by paco-sparta 0
  • Patch vulnerable gson lib

    Patch vulnerable gson lib

    Expected behaviour

    Actual behaviour

    Steps to reproduce

    Version of the library

    Deserialization of Untrusted Data [High Severity][https://security.snyk.io/vuln/SNYK-JAVA-COMGOOGLECODEGSON-1730327] in com.google.code.gson:[email protected]

    Gradle version

    opened by iiq374 0
  • Option to Fail if Snapshot is Missing

    Option to Fail if Snapshot is Missing

    Removed the template because this is a feature request rather than a bug report.

    It would be useful to have an option to fail an assertion, rather than generate a snapshot, if a snapshot is missing. This is especially useful for CI. It's also possible I'm missing something here but in local dev with the latest version of KotlinSnapshot, just deleting a snapshot will recreate it on the next test, which could create false negatives (no failure? false positive success?).

    Jest's default behavior is not to create snapshots without --updateSnapshots which I think is safe, but even just a flag would be useful.

    Please point me in the right direction if I'm missing something, which is totally possible, but it looks like the answer is currently no: https://github.com/pedrovgs/KotlinSnapshot/blob/69fba61ca93080866a7ee90713595e1ead0bf1ba/core/src/main/kotlin/com/karumi/kotlinsnapshot/core/Camera.kt#L28-L31

    enhancement help wanted good first issue 
    opened by bensaufley 5
  • Dev Documentation > File Structure

    Dev Documentation > File Structure

    As a new developer contributing, it's hard to follow how the duplicate folder structure

    For example, whats the difference between:

    this file in /src/main and this file in core/bin ????

    Let's add markdown documentation for this.

    enhancement help wanted 
    opened by lcombs15 1
  • Dev Documentation > Local maven repo

    Dev Documentation > Local maven repo

    As a new developer contributing, it's hard to follow how the Gradle setup works in this repo.

    Let's document this: https://github.com/Karumi/KotlinSnapshot/blob/bfd3cbfd6fbe07b0d028475a091d363ac72b2466/KotlinConsumer/build.gradle#L5

    Explain it, talk about it, ext in a markdown file somewhere.

    enhancement help wanted 
    opened by lcombs15 1
Releases(2.3.0)
Owner
Pedro Gómez
"Sometimes it is the people no one imagines anything of who do the things no one can imagine." - Alan Turing
Pedro Gómez
Powerful, elegant and flexible test framework for Kotlin with additional assertions, property testing and data driven testing

Kotest is a flexible and comprehensive testing tool for Kotlin with multiplatform support. To learn more about Kotest, visit kotest.io or see our quic

Kotest 3.8k Jan 3, 2023
Selenium locators for Java/Kotlin that resemble the Testing Library (testing-library.com).

Selenium Testing Library Testing Library selectors available as Selenium locators for Kotlin/Java. Why? When I use Selenium, I don't want to depend on

Luís Soares 5 Dec 15, 2022
Snapshot/Screenshot test example project

Snapshot Snapshot/Screenshot test example code using Showkase (https://github.com/airbnb/Showkase) Paparazzi (https://github.com/cashapp/paparazzi) Te

Anders Ullnæss 3 Nov 25, 2022
A programmer-oriented testing framework for Java.

JUnit 4 JUnit is a simple framework to write repeatable tests. It is an instance of the xUnit architecture for unit testing frameworks. For more infor

JUnit 8.4k Jan 9, 2023
Android Unit Testing Framework

Robolectric is the industry-standard unit testing framework for Android. With Robolectric, your tests run in a simulated Android environment inside a

Robolectric 5.6k Jan 3, 2023
A programmer-oriented testing framework for Java.

JUnit 4 JUnit is a simple framework to write repeatable tests. It is an instance of the xUnit architecture for unit testing frameworks. For more infor

JUnit 8.4k Dec 28, 2022
Morsa: Jetpack Compose UI Testing Framework

Morsa: Jetpack Compose UI Testing Framework Test library to ease UI testing with Jetpack Compose Purpose This library aims to add some useful wrappers

HyperDevs 10 Dec 3, 2022
Fixtures for Kotlin providing generated values for unit testing

A tool to generate well-defined, but essentially random, input following the idea of constrained non-determinism.

Appmattus Limited 191 Dec 21, 2022
Android UI Testing

User scenario testing for Android Robotium is an Android test automation framework that has full support for native and hybrid applications. Robotium

null 2.8k Dec 14, 2022
A set of AssertJ helpers geared toward testing Android.

AssertJ Android A set of AssertJ assertions geared toward testing Android. Deprecated The support libraries and play services are developing at a rate

Square 1.6k Jan 3, 2023
Android UI Testing

User scenario testing for Android Robotium is an Android test automation framework that has full support for native and hybrid applications. Robotium

null 2.7k Apr 8, 2021
Turbine is a small testing library for kotlinx.coroutines Flow.

A small testing library for kotlinx.coroutines Flow

Cash App 1.8k Jan 5, 2023
Lovely Systems Database Testing

Lovely DB Testing This repository provides opinionated testing helpers for database testing used at Lovely Systems. License This plugin is made availa

Lovely Systems GmbH 1 Feb 23, 2022
Project for testing intern candidate simple level

RickAndMorty-TestTask Тестовый проект 'Гайд по мультфильму Рик и Морти' для практикантов начального и продвинутого уровня. Структура проекта Структура

null 0 Nov 18, 2021
Very simple Morse API text translator. Mainly to do some testing with jitpack, API development etc.

Very simple Morse text translator API. Far from finish or even being reliable. Use for single sentence. Mainly to test github dependency for Android

Piotr Dymala 0 Dec 30, 2021
A set of TestRules and ActivityScenarios to facilitate UI Testing under given configurations: FontSizes, Locales

AndroidUiTestingUtils A set of TestRules, ActivityScenarios and utils to facilit

Sergio Sastre Flórez 122 Dec 23, 2022
Testify — Android Screenshot Testing

Testify — Android Screenshot Testing Add screenshots to your Android tests Expand your test coverage by including the View-layer. Testify allows you t

ndtp 43 Dec 24, 2022
PowerMock is a Java framework that allows you to unit test code normally regarded as untestable.

Writing unit tests can be hard and sometimes good design has to be sacrificed for the sole purpose of testability. Often testability corresponds to go

PowerMock 3.9k Jan 5, 2023
A powerful test framework for Android

Cafe A powerful test framework for Android named Case Automated Framework for Everyone. Home Page http://baiduqa.github.com/Cafe/ How to make Cafe dow

Baidu 367 Nov 22, 2022