how can I combine tests or reuse data in spock - spock

I have a situation where I want to create some data using a post request and then use the result of that post in few other tests, is it possible to do this using spock. From the below I would like to reuse the response (or something thats extracted from it. How can I do that?
def "AddAssessment valid output case"() {
given:
def request = """
{
"assessmentTopic" : "$assessmentTopic",
"assessmentSubTopic" : "$assessmentSubTopic",
"duration" : "$duration",
"assessmentLevel" : "$assessmentLevel"
}
"""
when:
def result = mvc.perform(
MockMvcRequestBuilders
.post("/assessments/createAssessment")
.content(request)
.contentType(MediaType.APPLICATION_JSON)
)
then:
MvcResult response = result
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.jsonPath('$.*').doesNotExist())
.andReturn()
where:
assessmentTopic | assessmentSubTopic | duration | assessmentLevel
"Maths" | "Calculus" | "120" | "1"
"Civils" | "History" | "500" | "4"
"UPSC" | "GK" | "120" | "5"
"Chemistry" | "Elements" | "120" | "3"
}

Have you considered making the response you wish to re-use a #Shared variable and initialise it in a setupSpec() method? That is what you usually do if within one specification you wish to share data between feature methods.
See also the Spock manual, section "Fields":
#Shared res = new VeryExpensiveResource()
Sometimes you need to share an object between feature methods. For example, the object might be very expensive to create, or you might want your feature methods to interact with each other. To achieve this, declare a #Shared field. Again it’s best to initialize the field right at the point of declaration. (Semantically, this is equivalent to initializing the field at the very beginning of the setupSpec() method.)

Related

What are the step by step actions needed to upgrade a holochain rust back end from 0.0.1 to 0.0.2?

I started by reviewing the Api notes and comparing them:
https://developer.holochain.org/api/
What I have done so far:
Preparation:
Downloaded and installed 0.0.2, and then updated the bash_profile following this link:
https://developer.holochain.org/start.html
JSON PARSE/Stringify update
Updated all of the tests to remove any JSON.parse and JSON.stringify calls as they are no longer needed, for example replacing this:
JSON.stringify({})
with this:
{}
Derive function update
Updated all derive functions in zome definition files ( lib.rs ) to include Debug and DefaultJSON, like this:
#[derive(Serialize, Deserialize, Debug, DefaultJson)]
Json String update
Did a global find and replace for all zome files on the JsonString
changing the serde_json call to look like this :
replacing
-> serde_json::Value
with
-> JsonString
so it looks like this:
fn handle_create_action(action: Action, user_address: HashString) ->
JsonString { ...
Current errors
I am running to these errors:
error: cannot find derive macro DefaultJson in this scope
error[E0412]: cannot find type JsonString in this scope
how can we import these into the lib.rs files?
Update
This is by no means a comprehensive answer, but here are some of the additional steps I have found with help.
You will also need to edit the cargo.toml file of each zome, the dependencies part, to look like this:
serde = "1.0"
serde_json = "1.0"
serde_derive = "1.0"
hdk = { git = "https://github.com/holochain/holochain-rust", branch = "master" }
holochain_core_types = { git = "https://github.com/holochain/holochain-rust", branch = "master" }
holochain_core_types_derive = { git = "https://github.com/holochain/holochain-rust", branch = "master" }
holochain_wasm_utils = { git = "https://github.com/holochain/holochain-rust", branch = "master" }
This was found with the specification app which is already up to date with the release that happened last night, at this page:
https://github.com/holochain/dev-camp-tests-rust/blob/master/zomes/people/code/Cargo.toml
Each zome needed this as a replacement for everything above the #derive function:
#![feature(try_from)]
#[macro_use]
extern crate hdk;
extern crate serde;
#[macro_use]
extern crate serde_derive;
#[macro_use]
extern crate serde_json;
extern crate holochain_core_types;
#[macro_use]
extern crate holochain_core_types_derive;
use hdk::{
holochain_core_types::{
dna::zome::entry_types::Sharing,
hash::HashString,
json::JsonString,
entry::Entry,
entry::entry_type::EntryType,
error::HolochainError,
cas::content::Address,
},
};
This resolved the initial errors on compile, and showed me the next layer of changes needed via terminal feedback when I ran hc test to compile, build and test the app... this is what I am seeing now..
Error 1
error[E0061]: this function takes 1 parameter but 2 parameters were supplied
--> src/lib.rs:56:11
|
56 | match hdk::commit_entry("metric", json!(metric)) {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected 1 parameter
Error 2
error[E0308]: mismatched types
--> src/lib.rs:60:24
|
60 | return json!({"link error": link_result.err().unwrap()});
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected struct `holochain_core_types::json::JsonString`, found enum `serde_json::Value`
I will attempt to resolve this one by replacing the serde_json calls in the zome code with JsonString...
Error 3
error[E0609]: no field `links` on type `hdk::holochain_wasm_utils::api_serialization::get_links::GetLinksResult`
--> src/lib.rs:82:18
|
82 | .links
| ^^^^^ unknown field
Error 4
error[E0599]: no method named `to_json` found for type `hdk::error::ZomeApiError` in the current scope
--> src/lib.rs:97:32
|
97 | "error": hdk_error.to_json()
| ^^^^^^^
Update 2
#connorturlands answer got me through most of those errors, and now there appears to be just one more.
^^^^^^^^
|
= note: #[warn(unused_imports)] on by default
error[E0063]: missing fields `active`, `date_time`, `description` and 12 other fields in initializer of `Metric`
--> src/lib.rs:48:68
|
48 | let metric_entry = Entry::new(EntryType::App("metric".into()), Metric{
| ^^^^^^ missing `active`, `date_time`, `description` and 12 other fields
error: aborting due to previous error
For more information about this error, try `rustc --explain E0063`.
error: Could not compile `metrics`.
Which is in response to this zome definition:
fn handle_create_metric(metric: Metric, user_address: HashString) -> JsonString {
let metric_entry = Entry::new(EntryType::App("metric".into()), Metric{
// => Here is where the error triggers... it wants me to define 'title, time, etc' but as a core function, I don't see the point, those will be inputted.. not sure how to address this
});
match hdk::commit_entry(&metric_entry) {
Ok(metric_address) => {
match hdk::link_entries(
&user_address,
&metric_address,
"metric_by_user"
) {
Ok(link_address) => metric_address.into(),
Err(e) => e.into(),
}
}
Err(hdk_error) => hdk_error.into(),
}
}
For error 1, just check this example, and copy it:
https://developer.holochain.org/api/0.0.2/hdk/api/fn.commit_entry.html
For error 2, just do
link_result.into()
which converts it into a JsonString
For error 3, use
.addresses()
instead of .links, this can be seen here: https://developer.holochain.org/api/0.0.2/holochain_wasm_utils/api_serialization/get_links/struct.GetLinksResult.html
And for error 4 just do
hdk_error.into()
and remove it from the json! wrapping that it looks like you're attempting :)
In general, if you see a reference to something relating to the hdk, use the search feature of the API ref to find out more about it, its very good
Migrating from 0.0.1 to 0.0.2 was exactly what I did recently for the todo-list example. I just created a branch for the old version so you can compare the two
https://github.com/willemolding/holochain-rust-todo
https://github.com/willemolding/holochain-rust-todo/tree/working-on-v0.0.1
From memory some of the gotchas are:
commit_entry now takes a single reference to an Entry object
Links must be included as part of the define_zome! or they cannot be created
move from serde_json Value to JsonString
Need to include holochain_core_types_derive = { git = "https://github.com/holochain/holochain-rust" , tag = "holochain-cmd-v0.0.2" } in the cargo.toml
Responses from get_entry are generic entry types and can be converted into a local type, say ListEntry, as ListEntry::try_from(entry.value())
There are a whole lot more so its probably best to check out the repo.

Why any Geb page method or module method can not be accessed in Where block

I have a Page class and having some method which extract the Web page. Now I want to call these method into the Where block of Spock to pass as data provider. But when I call the method then it throws an error as it did not find it. But the same is accessible from out of Where block. Why is it so?
Example
def "Validate all article has its id"(){
when:"I am at Overview page"
at OverviewPage
then:"I should the article id of all article"
getAllCountOf(articleInfo).size() == actualCount
where:
articleInfo |actualCount
TopArticleListInfo.ARTICLE_THUMBNAIL |filter.getCount()
}
In the above code 'filter.getCount()' can not be accessed from Where block, But the same method is accessible in when or then block.
i want to understand the logic behind the scene It looks like , Where block is not able to find this method statically, need to create an object to call this.
When I tried the solution provided by erdi, But that also not solving the issue
when:"I am at Overview page"
at OverviewPage
then:"I should the article id of all article"
getAllCountOf(articleInfo).size() == page."$actualCount"
where:
articleInfo |actualCount
TopArticleListInfo.ARTICLE_THUMBNAIL |'getRowCount().size()'
Here getRowCount().size() substitute in "$actualCount". But still it throwing the error
Error Message
getAllCountOf(articleInfo).size() == page."$actualCount"
| | | | |
| | 10 | groovy.lang.MissingPropertyException: Unable to resolve getRowCount().size() as content for inca.sundashboard.pageobject.OverviewPage, or as a property on its Navigator context. Is getRowCount().size() a class you forgot to import?
|
I'm using Dynamic Method Names in my tests, here is a small example:
def "Validate all article has its id"(){
when: "I am at Overview page"
at OverviewPage
then: "I should the article id of all article"
getAllCountOf(articleInfo).size() == "$actualCountMethod"().getCount()
where:
articleInfo | actualCountMethod
TopArticleListInfo.ARTICLE_THUMBNAIL | filter
}

Grails 2.3.6 IntegrationSpec Spock test can't modify controller.request.JSON when using #Stepwise

I have an IntegrationSpec test in Grails 2.3.6, which creates an instance of any controller, adds data to the body (via controller.request.JSON), and then validates that it was properly set.
The problem is that when I add the #Stepwise annotation, it seems to lock the request object on the controller object. In the debugger I see it is the same object (based on the hashcode), and as you can see by the test failure below, the second time the test runs with a value from the where: block, it fails, because the first value from the where: block is still present.
#Stepwise
class TestSpec extends IntegrationSpec {
#Unroll
void "changing controller request"() {
setup:
SomeController controller = new SomeController()
when:
controller.request.JSON = json
then:
controller.request.JSON == json
where:
json << [
[one: "1"],
[two: "2"]
]
}
}
This is the failing message.
controller.request.JSON == json
| | | | |
| | | | [two:2]
| | | false
| | [one:1]
| org.codehaus.groovy.grails.plugins.testing.GrailsMockHttpServletRequest#55906c66
com.package.SomeController#7b044602
This does not fail if I remove the #Stepwise.
Is there any way to force the request object to be recreated, or to override a value set by a previous test?
Never fails - an issue plagues me for hours, I post it here, and then figure out the solution.
So I looked into IntegrationSpec, which is a grails specific class to handle Spock Specifications in an integration environment. I noticed that the setupSpec() and setup() methods will initialize the request object by making a call to initRequestEnv(). Unfortunately, this only happens if this is not a test with #Stepwise - I'm not sure why.
The solution is to add this check manually in your test, by adding the following lines in the test, which will recreate the request object for each test.
private GrailsTestRequestEnvironmentInterceptor perMethodRequestEnvironmentInterceptor = null
def setup() {
perMethodRequestEnvironmentInterceptor = initRequestEnv()
}
def cleanup() {
perMethodRequestEnvironmentInterceptor?.destroy()
}

Is it possible to programmatically add lines to a scenario?

I would like to add the same line to the start of each one of my SpecFlow tests.
This line specifies a list of several scenarios which will change over time, and therefore it is not feasible to maintain this list for every test.
For example:
Given I have set my site theme to <MyTheme>
|Theme Names|
|Theme 1 |
|Theme 2 |
|Theme 3 |
|Theme 4 |
|Theme 5 |
I'd like to have this test repeat for each of the themes. The list of themes is not set in stone, and should be maintained in a single place.
So far, I've successfully managed to create a Generator Plugin, and I was planning on using this plugin to alter the SpecFlow feature immediately before the Test Class is generated. However, I can't see a way to edit the scenario from within this context.
Is it possible to get and set the scenario text from within an implementation of IUnitTestGeneratorProvider?
I'm not set on using this method, so if anyone can suggest a better way to do this then I'd accept that too.
Apologies if I've gotten some terminology wrong- I've only just started using SpecFlow.
Edit:
I'm adding this section to provide clarification as to what I'm actually after.
Suppose I had a test suite containing 800 tests. We have a business requirement to run each of those 800 tests on each of our available themes. The list of available themes can change at any time, and it would be unfeasible to maintain this list in more than a single location.
So, for example, if I had the following two tests:
Example A:
Given I set context to < site >
Given I go to base url
When I type <username> in username field
When I type <password> in password field
When I click login button
Examples:
| site | username | password |
| MySuperSite | chris | mypassword |
| MySuperSite2 | chris2 | mypassword |
Given I am logged in
Given I go to base url
When I click logout button
Then I am logged out
I could simply manually change these tests to something along the lines of:
Example B:
Given I am using the < theme > theme
Given I set context to < site >
Given I go to base url
When I type <username> in username field
When I type <password> in password field
When I click login button
Examples:
| site | username | password | theme |
| MySuperSite | chris | mypassword | theme1 |
| MySuperSite2 | chris2 | mypassword | theme1 |
| MySuperSite | chris | mypassword | theme2 |
| MySuperSite2 | chris2 | mypassword | theme2 |
| MySuperSite | chris | mypassword | theme3 |
| MySuperSite2 | chris2 | mypassword | theme3 |
Given I am using the < theme > theme
Given I am logged in
Given I go to base url
When I click logout button
Then I am logged out
Examples:
| theme |
| theme1 |
| theme2 |
| theme3 |
There are a few issues with this:
The tests become bloated with data that is repeated
If we no longer choose to support theme2 then someone will have to go through each test and remove it from the examples table (not too bad in the example above, but imaging we had >800 tests)
Tests that already have example tables will have their table size multiplied by the number of themes we are supporting (>40)
There is a large risk of manual error caused by someone inadvertently forgetting to add a specific theme to a test
Objective:
I'd like to be able to have our testers write tests in the style of Example A, but have the tests themselves compile down to how they would if they were written in the style of Example B.
I've created a generator plugin for specflow, with a view of intercepting the test creation and then programmatically adding the line Given I am using the < theme > theme, and then update or add any example data as required. However, I can't seem to be able to do this from here.
Can anyone tell me if this is possible, and if so, how should I go about it?
OK, I figured this out. It took a few steps:
After looking through the source code on GitHub, I found UnitTestFeatureGenerator which appears to be the class responsible for turning the specflow files into unit tests.
I then created a new class that inherited from UnitTestFeatureGenerator and hides the GenerateUnitTestFixture method from the base class.
In the body of my GenerateUnitTestFixture class, I then add the extra steps required into the scenario before handing off of base.GenerateUnitTestFixture to generate the unit tests. Here's the gist of it:
public class MultiThemeUnitTestFeatureGenerator : UnitTestFeatureGenerator, IFeatureGenerator
{
public MultiThemeUnitTestFeatureGenerator(IUnitTestGeneratorProvider testGeneratorProvider, CodeDomHelper codeDomHelper, GeneratorConfiguration generatorConfiguration, IDecoratorRegistry decoratorRegistry)
: base(testGeneratorProvider, codeDomHelper, generatorConfiguration, decoratorRegistry)
{}
public new CodeNamespace GenerateUnitTestFixture(Feature feature, string testClassName, string targetNamespace)
{
foreach (var scenario in feature.Scenarios)
{
scenario.Steps.Insert(0, new Given {Text = "Given I have <Theme> set as my current theme"});
//add any other steps you need....
}
return base.GenerateUnitTestFixture(feature, testClassName, targetNamespace);
}
}
Once I had all of this set up, I needed a way to tell specflow to use my new class instead of the currently registered UnitTestFeatureGenerator. This was the complicated bit to get working as the documentation pretty much just said "Coming soon". Thankfully, I found an excellent blog post which outlines all the pitfalls.
My IGeneratorPlugin implementation looks like this:
public class MultiThemeGeneratorPlugin : IGeneratorPlugin
{
public void RegisterDependencies(ObjectContainer container)
{}
public void RegisterCustomizations(ObjectContainer container, SpecFlowProjectConfiguration generatorConfiguration)
{
container.RegisterTypeAs<MultiThemeFeatureGeneratorProvider, IFeatureGeneratorProvider>("default");
}
public void RegisterConfigurationDefaults(SpecFlowProjectConfiguration specFlowConfiguration)
{}
}
Note That I register IFeatureGeneratorProvider as opposed to IFeatureGenerator. I had to create an implementation of IFeatureGeneratorProvider that returns an instance of the implementation of IFeatureGenerator that I was interested in:
public class MultiThemeFeatureGeneratorProvider : IFeatureGeneratorProvider
{
private readonly ObjectContainer _container;
public MultiThemeFeatureGeneratorProvider(ObjectContainer container)
{
_container = container;
}
public int Priority
{
get { return int.MaxValue; }
}
public bool CanGenerate(Feature feature)
{
return true;
}
public IFeatureGenerator CreateGenerator(Feature feature)
{
return _container.Resolve<MultiThemeUnitTestFeatureGenerator>();
}
}
I can't think of a simple way to do this, but my approach would probably be to exclude the theme from the tests but have the build server run the tests in an environment where the theme is already set up (by an environment variable or via some config file or similar) and then run the entire suite n times, once for each theme.
So I would have the scenarios look like this
Given I am using the currently configured theme
Given I am logged in
Given I go to base url
When I click logout button
Then I am logged out
where the first step reads the theme from the config file or environment variable and sets up the tests to use that theme.
Once the build server has run these tests, it would change the config/environment variable to the next theme and the run the suite again.
This gives you the ability to specify the themes in a single place (on the build server, or in a file that the BS uses to discover the themes) and have the tests run using each of those themes. It also might give you some performance advantage as you could parallelize the tests on the build server as each one would be a different test step and each test step could be run independently and on different machines.
For the devs you would only be able to test one theme at once and would have to manually alter the config/env variable to change to a different theme, which might not be ideal.
Someone else might have a better idea of how to do this in the way you are envisioning, fingers crossed

How to avoid null field errors in spock for Grails domain mock

Using grails 2.2.0 given this simple Domain
class Order {
static mapping = {table "ticket_order"}
String foo
}
And associated spock test
#TestFor(Order)
class OrderSpec extends Specification {
def "sensible constraints for order class"() {
setup:
mockForConstraintsTests(Order)
when:
def order = new Order(
foo : foo
)
order.validate()
then:
!order.errors.hasFieldErrors("foo")
where:
foo = "bar"
}
}
I get this output
grails> test-app unit: Order -echoOut
| Running 1 spock test... 1 of 1
--Output from sensible constraints for order class--
| Failure: sensible constraints for order class(uk.co.seoss.presscm.OrderSpec)
| Condition not satisfied:
!order.errors.hasFieldErrors("foo")
|| | |
|| | true
|| org.codehaus.groovy.grails.plugins.testing.GrailsMockErrors: 1 errors
|| Field error in object 'uk.co.seoss.presscm.Order' on field 'foo': rejected value [null];
Could someone please explain why I'm getting that null, have I not set the property correctly? I've tried a few simpler formulations of this with no joy. It works fine in a standard unit test.
It looks to me like your mixing the data-driven and interaction based testing styles.
The where block is mentioned only in the context of data-driven, and the when/then combo in the context of interaction testing.
Try put def foo = "bar" at the top of the test.

Resources