How can I get JenkinsPipelineUnit to intercept both text() and string() param calls? I have code that triggers a build with a few params. I want to write a nice unit test to check that it does what it should. However, the string() calls are not intercepted so I cannot test them. I can test the text() call.
Yes, it is possible to write unit tests for Jenkins pipeline code vs testing via jenkins production jobs. This project + shared libraries makes jenkins into a much better tool.
Any ideas on how I could do this? I looked in the JenkinsPipelineUnit project but didn't find an example that fit and couldn't figure out where to look in the runtime objects.
I do see that the project's BasePipelineTest.groovy links string to its stringInterceptor which seems to just eat it the string. Maybe, I can unregister theirs...
Example
def triggeringParams = [:]
....
for (def param in ['text', 'string']) {
helper.registerAllowedMethod(param, [LinkedHashMap],
{ LinkedHashMap data ->
triggeringParams << data
}
)
}
thisScript = helper.loadScript('''
package resources
return this''')
def params = []
params << thisScript.text(name: 'MULTILINE_PARAM', value: '\nline1\nline2')
params << thisScript.string(name: 'STRING_PARAM', value: 'a string')
thisScript.build(job: 'myJob', parameters: params)
println triggeringParams
Results
[
[name:JOB_PROPERTIES, value:
line1
line2]
]
Wrong type was the problem. The project's BasePipelineTest.groovy links string to its stringInterceptor which seems to just eat it the string and uses register Map not LinkedHashMap. So, the first is found before mine and boom, the string doesn't show in my collector.
If I modify my code to use the more generic map it works
void addParameterHelpers() {
for (def param in ['text', 'string']) {
helper.registerAllowedMethod(param, [Map],
{ LinkedHashMap data ->
triggeringParams << data
}
)
}
}
Related
I am very new to testing and I'm struggling my way through all this new stuff I am learning. Today I want to write a test for a vuetify <v-text-field> component like this:
<v-text-field
v-model="user.caption"
label="Name"
:disabled="!checkPermissionFor('users.write')"
required
/>
my test should handle the following case:
an active, logged in user has a array in vuex store which has his permissions as a array of strings. exactly like this
userRights: ['dashboard', 'imprint', 'dataPrivacy']
the checkPermissionFor() function is doing nothing else then checking the array above with a arr.includes('x')
after it came out the right is not included it gives me a negotiated return which handles the disabled state on that input field.
I want to test this exact scenario.
my test at the moment looks like this:
it('user has no rights to edit other user overview data', () => {
const store = new Vuex.Store({
state: {
ActiveUser: {
userData: {
isLoggedIn: true,
isAdmin: false,
userRights: ['dashboard', 'imprint', 'dataPrivacy']
}
}
}
})
const wrapper = shallowMount(Overview, {
store,
localVue
})
const addUserPermission = wrapper.vm.checkPermissionFor('users.write')
const inputName = wrapper.find(
'HOW TO SELECT A INPUT LIKE THIS? DO I HAVE TO ADD A CLASS FOR IT?'
)
expect(addUserPermission).toBe(false)
expect(inputName.props('disabled')).toBe(false)
})
big questions now:
how can I select a input from vuetify which has no class like in my case
how can I test for "is the input disabled?"
wrapper.find method accepts a query string. You can pass a query string like this :
input[label='Name'] or if you know the exact index you can use this CSS query too : input:nth-of-type(2).
Then find method will return you another wrapper. Wrapper has a property named element which returns the underlying native element.
So you can check if input disabled like this :
const buttonWrapper = wrapper.find("input[label='Name']");
const isDisabled = buttonWrapper.element.disabled === true;
expect(isDisabled ).toBe(true)
For question 1 it's a good idea to put extra datasets into your component template that are used just for testing so you can extract that element - the most common convention is data-testid="test-id".
The reason you should do this instead of relying on the classes and ids and positional selectors or anything like that is because those selectors are likely to change in a way that shouldn't break your test - if in the future you change css frameworks or change an id for some reason, your tests will break even though your component is still working.
If you're (understandably) worried about polluting your markup with all these data-testid attributes, you can use a webpack plugin like https://github.com/emensch/vue-remove-attributes to strip them out of your dev builds. Here's how I use that with laravel mix:
const createAttributeRemover = require('vue-remove-attributes');
if (mix.inProduction()) {
mix.options({
vue: {
compilerOptions: {
modules: [
createAttributeRemover('data-testid')
]
}
}
})
}
as for your second question I don't know I was googling the same thing and I landed here!
One of the features of Akka HTTP (formally known as Spray) is its ability to automagically marshal and unmarshal data back and forth from json into case classes, etc. I've had success at getting this to work well.
At the moment, I am trying to make an HTTP client that performs a GET request with query parameters. The code currently looks like this:
val httpResponse: Future[HttpResponse] =
Http().singleRequest(HttpRequest(
uri = s"""http://${config.getString("http.serverHost")}:${config.getInt("http.port")}/""" +
s"query?seq=${seq}" +
s"&max-mismatches=${maxMismatches}" +
s"&pam-policy=${pamPolicy}"))
Well, that's not so pretty. It would be nice if I could just pass in a case class containing the query parameters, and have Akka HTTP automagically generate the query parameters, kind of like it does for json. (Also, the server side of Akka HTTP has a somewhat elegant way of parsing GET query parameters, so one would think that it would also have a somewhat elegant way to generate them.)
I'd like to do something like the following:
val httpResponse: Future[HttpResponse] =
Http().singleRequest(HttpRequest(
uri = s"""http://${config.getString("http.serverHost")}:${config.getInt("http.port")}/query""",
entity = QueryParams(seq = seq, maxMismatches = maxMismatches, pamPolicy = pamPolicy)))
Only, the above doesn't actually work.
Is what I want doable somehow with Akka HTTP? Or do I just need to do things the old-fashioned way? I.e, generate the query parameters explicitly, as I do in the first code block above.
(I know that if I were to change this from a GET to a POST, I could probably to get it to work more like I would like it to work, since then I could get the contents of the POST request automagically converted from a case class to json, but I don't really wish to do that here.)
You can leverage the Uri class to do what you want. It offers multiple ways to get a set of params into the query string using the withQuery method. For example, you could do something like this:
val params = Map("foo" -> "bar", "hello" -> "world")
HttpRequest(Uri(hostAndPath).withQuery(params))
Or
HttpRequest(Uri(hostAndPath).withQuery(("foo" -> "bar"), ("hello" -> "world")))
Obviously this could be done by altering the extending the capability of Akka HTTP, but for what you need (just a tidier way to build the query string), you could do it with some scala fun:
type QueryParams = Map[String, String]
object QueryParams {
def apply(tuples: (String, String)*): QueryParams = Map(tuples:_*)
}
implicit class QueryParamExtensions(q: QueryParams) {
def toQueryString = "?"+q.map{
case (key,value) => s"$key=$value" //Need to do URL escaping here?
}.mkString("&")
}
implicit class StringQueryExtensions(url: String) {
def withParams(q: QueryParams) =
url + q.toQueryString
}
val params = QueryParams(
"abc" -> "def",
"xyz" -> "qrs"
)
params.toQueryString // gives ?abc=def&xyz=qrs
"http://www.google.com".withParams(params) // gives http://www.google.com?abc=def&xyz=qrs
I have been trying to figure this out for 2 days now and I am really stuck and frustrated. I have a domain object with a service which is being used for custom validation. The domain looks like this:
class Llama {
String name
transient myFetcherService
static transients = [
'myFetcherService'
]
static constraints = {
name validator: { val, obj ->
if (obj.nameExists(val) == true) {
//return some error here.
}
}
}
protected boolean nameExists(String name) {
List<Llama> llamasList = myFetcherService.fetchExistingLlamasByName(name)
if (llamasList.isEmpty()) {
return false
}
return true
}
}
Now, I have another Service, which simply saves a list of Llama objects. It looks like this:
class LlamaFactoryService {
public void createLlamas(List<String> llamaNames) {
llamaNames.each { name ->
new Llama(name: name).save()
}
}
}
In my test. I keep getting this error:
Failure: createLlamas should create Llammas (com.myLlamaProject.LlamaFactoryServiceSpec)
| java.lang.NullPointerException: Cannot invoke method myFetcherService on null object
I don't understand. In my tests, added a metaClass for the service in the "given" section. When it tries to save, it's telling that the service is null. This is what my test looks like:
given:
def myFetcherService = mockFor(MyFetcherService)
myFetcherService.demand.fetchExistingLlamasByName {def name -> return []}
Llama.metaClass.myFetcherService = myFetcherService.createMock()
when:
service.createLlamas(['Pablo','Juan','Carlos'])
then:
//some validations here....
I also tried using metaClass on the method nameExists() like:
Llama.metaClass.myFetcherService = { def name -> false }
, but it gives me the same nullPointerException as the one above. Could someone point me to the right direction? I'm a bit stuck. :(
Thanks in advance for reading and helping.
You're using a unit test and the general rule for unit tests is that beans generally aren't created for you, so you'll need to inject them yourself.
(Code edited to reflect the fact I misread the question)
I think you want a testing pattern something like:
given:
def mockMyFetcherService = Mock(MyFetcherService) // create the mock
Llama.metaClass.getMyFetcherService = { mockMyFetcherService } // inject the dependency
def returnList = [] // let's just define this here and you can re-use this pattern in other tests more easily
when:
service.createLlamas(['Pablo','Juan','Carlos'])
then:
// tell Spock what you expect to have happen with your Mock - return the List you defined above
3 * mockFetcherService.fetchExistingLlamasByName(_) >> returnList
If the injection of the service into the metaClass doesn't work (suggested here), you could always try using the defineBeans{} closure within the unit test itself (http://www.block-consult.com/blog/2011/08/17/inject-spring-security-service-into-domain-class-for-controller-unit-testing/).
Thus you could try:
defineBeans {
myFetcherService(MockMyFetcherService)
}
where MockMyFetcherService is defined in the same file that defines the test. This is the approach followed here:
See here for examples of more Spock interactions.
If you're using Grails 2.4.3 or below you'll need to put CGLIB in BuildConfig.groovy but I see here that it's already done for you in 2.4.4, so you should be ok just to use Mock(classname).
I am having a strange issue when mocking the log field of a class. Running the same test twice shows an error the second time. This is an example of code:
class AccountConfigJSON {
static Logger log = Logger.getLogger(AccountConfigJSON.class)
def AccountConfigJSON(String jsonString) {
if (jsonString) {
json = new JSONObject(jsonString)
} else {
log.debug("No JSON string for account config. Will not parse")
}
}
}
and this is the specification
class AccountConfigJSONUnitSpec extends UnitSpec {
def loggerMock
def setup(){
loggerMock = Mock(org.apache.log4j.Logger)
org.apache.log4j.Logger.metaClass.static.getLogger = { Class clazz -> loggerMock }
}
def 'If jsonString is null, a log is written'(){
when:
new AccountConfigJSON("")
then:
1 * loggerMock.debug("No JSON string for account config. Will not parse")
}
def 'If jsonString is empty, a log is written'(){
when:
new AccountConfigJSON("")
then:
1 * loggerMock.debug("No JSON string for account config. Will not parse")
}
}
The second test fails showing
| Too few invocations for:
1 * loggerMock.debug("No JSON string for account config. Will not parse") (0 invocations)
but debugging the app using Idea, clearly it runs this sentence. Any idea?
Looks odd that the actual call is executed but the interaction in not recorded. You can get around with it by explicitly assigning the mocked logger to the class as below:
def setup(){
loggerMock = Mock(org.apache.log4j.Logger)
AccountConfigJSON.log = loggerMock
}
From the definition of "interaction", I think the above setup is the best way to go.
Is an Interaction Just a Regular Method Invocation?
Not quite. While an interaction looks similar to a regular method
invocation, it is simply a way to express which method invocations are
expected to occur. A good way to think of an interaction is as a
regular expression that all incoming invocations on mock objects are
matched against. Depending on the circumstances, the interaction may
match zero, one, or multiple invocations.
This only happens while dealing with static object properties in a class. The moment logger is defined non-static in the class under test, everything works as expected without the work around.
Let's say I have a simple action in my controller that ends with:
render(contentType: "text/json") {
message = 'some text'
foo = 'bar'
}
It renders correctly, as per the JSON builder documentation. However, when I attempt to unit test that response in a ControllerUnitTest, I get a blank string with controller.response.contentAsString. I even tried controller.renderArgs, but that just contains contentType: "text/json".
When I convert the JSON to a map, and marshall it as JSON, then I can test properly. But is there a way to unit test the code as it stands?
you have to call the action in your tests and compare the results using controller.response.contentAsString
so your test method would look like
void testSomeRender() {
controller.someRender()
assertEquals "jsonString", controller.response.contentAsString
}
Take a look at this blog post http://www.lucasward.net/2011/03/grails-testing-issue-when-rendering-as.html
After much searching, I found that this is not possible in 1.3.7. Either have to upgrade to Grails 2.0, or override the controller metaClass as suggested in this post:
controller.class.metaClass.render = { Map map, Closure c ->
renderArgs.putAll(map)
switch(map["contentType"]) {
case null:
break
case "application/xml":
case "text/xml":
def b = new StreamingMarkupBuilder()
if (map["encoding"]) b.encoding = map["encoding"]
def writable = b.bind(c)
delegate.response.outputStream << writable
break
case "text/json":
new JSonBuilder(delegate.response).json(c)
break
default:
println "Nothing"
break
}
}