[{"data":1,"prerenderedAt":1804},["ShallowReactive",2],{"author-hugo-contreras":3,"author-articles-hugo-contreras":22,"authors":1488},{"id":4,"title":5,"body":6,"description":10,"extension":13,"meta":14,"name":15,"navigation":16,"path":17,"readingTime":18,"seo":19,"stem":20,"__hash__":21},"authors\u002Fauthors\u002Fhugo-contreras.md","Engineering Manager",{"type":7,"value":8,"toc":9},"minimark",[],{"title":10,"searchDepth":11,"depth":11,"links":12},"",2,[],"md",{},"Hugo Contreras",true,"\u002Fauthors\u002Fhugo-contreras",1,{"title":5,"description":10},"authors\u002Fhugo-contreras","2nc3VMu9ASq9Z6Pwx2-7-Ye991Pww4p-UEDBQFfjF-Q",[23,1312],{"id":24,"title":25,"author":26,"body":27,"date":1302,"description":1303,"extension":13,"lang":1304,"meta":1305,"navigation":16,"path":1306,"published":16,"readingTime":222,"seo":1307,"stem":1308,"tags":1309,"__hash__":1311},"articles\u002Farticles\u002F2022-10-06-3-different-ways-to-write-integration-tests-with-external-services-dependencies.md","3 different ways to write integration tests with external services dependencies","hugo-contreras",{"type":7,"value":28,"toc":1291},[29,34,38,41,45,48,55,135,138,143,225,228,232,239,250,407,413,424,428,431,437,446,449,661,672,933,943,1188,1199,1202,1205,1209,1214,1231,1235,1243,1246,1250,1264,1275,1287],[30,31,33],"h2",{"id":32},"context","Context",[35,36,37],"p",{},"Integration testing can be tricky to setup because of all external services that can be involved.\nFor example a database, a file storage in the cloud,... As a developer the cost of implementation\ncan be so big that we won't take the time to implement integration tests. But the benefits are, in\nmy opinion, worth the price especially for applications that are not updated very often. For\nexample, we will be able to upgrade NPM dependencies with a high level of confidence without having\nto manually test the application each time.",[35,39,40],{},"In this article we will focus, as an example, on two external services which are a MongoDB database\nand AWS S3. We will see the different ways of dealing with these services to be able to implement\nsome integration tests. This is not a full tutorial but more a reflexion about this problematic.",[30,42,44],{"id":43},"mock-and-in-memory-db","Mock and in memory DB",[35,46,47],{},"One of the easiest solutions to solve the problem is to mock the external services that we rely on.\nThis way we don't have to deal with a complex setup to be able to run our integration tests.",[49,50,51],"ul",{},[52,53,54],"li",{},"Mock API call to AWS S3",[56,57,61],"pre",{"className":58,"code":59,"language":60,"meta":10,"style":10},"language-ts shiki shiki-themes github-light github-dark","beforeEach(async () => {\n  getAwsLinkStub = sinon.stub().resolves(\"STUB_URL\");\n  aws.getReadSignedUrlWithAws = getAwsLinkStub;\n});\n","ts",[62,63,64,89,118,129],"code",{"__ignoreMap":10},[65,66,68,72,76,80,83,86],"span",{"class":67,"line":18},"line",[65,69,71],{"class":70},"sScJk","beforeEach",[65,73,75],{"class":74},"sVt8B","(",[65,77,79],{"class":78},"szBVR","async",[65,81,82],{"class":74}," () ",[65,84,85],{"class":78},"=>",[65,87,88],{"class":74}," {\n",[65,90,91,94,97,100,103,106,109,111,115],{"class":67,"line":11},[65,92,93],{"class":74},"  getAwsLinkStub ",[65,95,96],{"class":78},"=",[65,98,99],{"class":74}," sinon.",[65,101,102],{"class":70},"stub",[65,104,105],{"class":74},"().",[65,107,108],{"class":70},"resolves",[65,110,75],{"class":74},[65,112,114],{"class":113},"sZZnC","\"STUB_URL\"",[65,116,117],{"class":74},");\n",[65,119,121,124,126],{"class":67,"line":120},3,[65,122,123],{"class":74},"  aws.getReadSignedUrlWithAws ",[65,125,96],{"class":78},[65,127,128],{"class":74}," getAwsLinkStub;\n",[65,130,132],{"class":67,"line":131},4,[65,133,134],{"class":74},"});\n",[35,136,137],{},"One of the drawbacks of this solution is that we rely on our own interpretation of the results from\nthe external service. For example, if the API of AWS is changing, our tests won't fail but it will\nnot work at runtime because there is a change in the API response that we are not aware of. Also\nmocking the responses can be tedious and time consuming especially when we have a lot of them.",[49,139,140],{},[52,141,142],{},"MongoDB in memory",[56,144,146],{"className":58,"code":145,"language":60,"meta":10,"style":10},"this.mongod = await MongoMemoryServer.create({\n  instance: {\n    dbName: \"myDb\",\n    storageEngine: \"ephemeralForTest\",\n  },\n  binary: {\n    version: mongoVersion,\n  },\n});\n",[62,147,148,171,176,187,197,203,209,215,220],{"__ignoreMap":10},[65,149,150,154,157,159,162,165,168],{"class":67,"line":18},[65,151,153],{"class":152},"sj4cs","this",[65,155,156],{"class":74},".mongod ",[65,158,96],{"class":78},[65,160,161],{"class":78}," await",[65,163,164],{"class":74}," MongoMemoryServer.",[65,166,167],{"class":70},"create",[65,169,170],{"class":74},"({\n",[65,172,173],{"class":67,"line":11},[65,174,175],{"class":74},"  instance: {\n",[65,177,178,181,184],{"class":67,"line":120},[65,179,180],{"class":74},"    dbName: ",[65,182,183],{"class":113},"\"myDb\"",[65,185,186],{"class":74},",\n",[65,188,189,192,195],{"class":67,"line":131},[65,190,191],{"class":74},"    storageEngine: ",[65,193,194],{"class":113},"\"ephemeralForTest\"",[65,196,186],{"class":74},[65,198,200],{"class":67,"line":199},5,[65,201,202],{"class":74},"  },\n",[65,204,206],{"class":67,"line":205},6,[65,207,208],{"class":74},"  binary: {\n",[65,210,212],{"class":67,"line":211},7,[65,213,214],{"class":74},"    version: mongoVersion,\n",[65,216,218],{"class":67,"line":217},8,[65,219,202],{"class":74},[65,221,223],{"class":67,"line":222},9,[65,224,134],{"class":74},[35,226,227],{},"This is a good solution, even though there are some limitations on performances for example, but it\nshould not be a problem in most cases. One of the drawbacks could be that this is not exactly the\nsame database as what we have in production so the behaviour can be different between the two\nenvironments.",[30,229,231],{"id":230},"github-actions-services","GitHub Actions Services",[35,233,234,235,238],{},"In GitHub Actions there is a concept of \"services” which are like ",[62,236,237],{},"docker-compose"," services but not\nexactly the same. This allows us to \"deploy” our services as we would have them in production. Then\nour code can run the same way as it does in production which is really handy to write integration\ntests.",[35,240,241,242,249],{},"Below you can find an example of configuration for GitHub Actions. There is an actual MongoDB\ncontainer running and exposing port 27017 to be able to request, save and delete data from a\ndatabase as we would do it in production. Also, we have a ",[243,244,248],"a",{"href":245,"rel":246},"https:\u002F\u002Fmin.io\u002F",[247],"nofollow","MinIO"," service which is\nan open source object storage solution that expose the same API as AWS S3.",[56,251,255],{"className":252,"code":253,"language":254,"meta":10,"style":10},"language-yml shiki shiki-themes github-light github-dark","jobs:\n  ci:\n    runs-on: ubuntu-latest\n    services:\n      mongodb:\n        image: mongo:5.0.4\n        ports:\n          - 27017:27017\n      minio:\n        image: minio\u002Fminio:latest\n        ports:\n          - 9000:9000\n        env:\n          MINIO_ROOT_USER: MINIO_ROOT_USER\n          MINIO_ROOT_PASSWORD: MINIO_ROOT_PASSWORD\n          MINIO_ACCESS_KEY: MINIO_ACCESS_KEY\n          MINIO_SECRET_KEY: MINIO_SECRET_KEY\n","yml",[62,256,257,266,273,284,291,298,308,315,323,330,340,347,355,363,374,385,396],{"__ignoreMap":10},[65,258,259,263],{"class":67,"line":18},[65,260,262],{"class":261},"s9eBZ","jobs",[65,264,265],{"class":74},":\n",[65,267,268,271],{"class":67,"line":11},[65,269,270],{"class":261},"  ci",[65,272,265],{"class":74},[65,274,275,278,281],{"class":67,"line":120},[65,276,277],{"class":261},"    runs-on",[65,279,280],{"class":74},": ",[65,282,283],{"class":113},"ubuntu-latest\n",[65,285,286,289],{"class":67,"line":131},[65,287,288],{"class":261},"    services",[65,290,265],{"class":74},[65,292,293,296],{"class":67,"line":199},[65,294,295],{"class":261},"      mongodb",[65,297,265],{"class":74},[65,299,300,303,305],{"class":67,"line":205},[65,301,302],{"class":261},"        image",[65,304,280],{"class":74},[65,306,307],{"class":113},"mongo:5.0.4\n",[65,309,310,313],{"class":67,"line":211},[65,311,312],{"class":261},"        ports",[65,314,265],{"class":74},[65,316,317,320],{"class":67,"line":217},[65,318,319],{"class":74},"          - ",[65,321,322],{"class":113},"27017:27017\n",[65,324,325,328],{"class":67,"line":222},[65,326,327],{"class":261},"      minio",[65,329,265],{"class":74},[65,331,333,335,337],{"class":67,"line":332},10,[65,334,302],{"class":261},[65,336,280],{"class":74},[65,338,339],{"class":113},"minio\u002Fminio:latest\n",[65,341,343,345],{"class":67,"line":342},11,[65,344,312],{"class":261},[65,346,265],{"class":74},[65,348,350,352],{"class":67,"line":349},12,[65,351,319],{"class":74},[65,353,354],{"class":113},"9000:9000\n",[65,356,358,361],{"class":67,"line":357},13,[65,359,360],{"class":261},"        env",[65,362,265],{"class":74},[65,364,366,369,371],{"class":67,"line":365},14,[65,367,368],{"class":261},"          MINIO_ROOT_USER",[65,370,280],{"class":74},[65,372,373],{"class":113},"MINIO_ROOT_USER\n",[65,375,377,380,382],{"class":67,"line":376},15,[65,378,379],{"class":261},"          MINIO_ROOT_PASSWORD",[65,381,280],{"class":74},[65,383,384],{"class":113},"MINIO_ROOT_PASSWORD\n",[65,386,388,391,393],{"class":67,"line":387},16,[65,389,390],{"class":261},"          MINIO_ACCESS_KEY",[65,392,280],{"class":74},[65,394,395],{"class":113},"MINIO_ACCESS_KEY\n",[65,397,399,402,404],{"class":67,"line":398},17,[65,400,401],{"class":261},"          MINIO_SECRET_KEY",[65,403,280],{"class":74},[65,405,406],{"class":113},"MINIO_SECRET_KEY\n",[35,408,409,410,412],{},"This is a great solution to facilitate the writing of integration tests but one of the caveat is\nthat we have to run the same container manually in local to be able to run the tests. So we will\nneed a ",[62,411,237],{}," file that exposes the same container that we will have in our CI.",[35,414,415,416,419,420,423],{},"The GitHub Actions also have some limitations: for example we can't explicitly define the command to\nexecuted inside the container which can be annoying when the ",[62,417,418],{},"Dockerfile"," doesn't set a ",[62,421,422],{},"CMD",".\nFinally, this relies on the CI tool, which is GitHub Actions in our example, but not all companies\nuse it...",[30,425,427],{"id":426},"testcontainers","TestContainers",[35,429,430],{},"As we have seen, the GitHub Actions services are a good solution to our problem but the main issue\nis that it relies on the fact that you are using GitHub which may not be the case. The good news is\nthat if your test environnement supports Docker, you can benefit from the same kind of advantages as\nthe GitHub Actions services but directly inside your code. At least this is the promise of\nTestContainers, a solution to help us, developers, to setup and write integration tests. From their\nwebsite, TestContainers is described as:",[432,433,434],"blockquote",{},[35,435,436],{},"Testcontainers is a Java library that supports JUnit tests, providing lightweight, throwaway\ninstances of common databases, Selenium web browsers, or anything else that can run in a Docker\ncontainer.",[35,438,439,440,445],{},"The other good news is that it exists\n",[243,441,444],{"href":442,"rel":443},"https:\u002F\u002Fgithub.com\u002Ftestcontainers\u002Ftestcontainers-node",[247],"a version for NodeJS",". The main advantage\nbeing that it's a CI agnostic solution as long as you have access to the Docker daemon.",[35,447,448],{},"From a code standpoint it looks a bit like this:",[56,450,452],{"className":58,"code":451,"language":60,"meta":10,"style":10},"describe(\"testContainer\", () => {\n  let mongoContainer: StartedTestContainer;\n  let mongoClient;\n\n  let minioContainer: StartedTestContainer;\n  let minioClient;\n\n  beforeAll(async () => {\n    mongoContainer = await startMongoContainer();\n    mongoClient = await getMongoClient({ container: mongoContainer });\n\n    minioContainer = await startMinioContainer();\n    minioClient = await getMinioClient({ container: minioContainer });\n  });\n\n  afterAll(async () => {\n    await mongoContainer.stop();\n    await minioContainer.stop();\n  });\n});\n",[62,453,454,471,488,495,500,513,520,524,539,554,569,573,587,602,607,611,626,639,651,656],{"__ignoreMap":10},[65,455,456,459,461,464,467,469],{"class":67,"line":18},[65,457,458],{"class":70},"describe",[65,460,75],{"class":74},[65,462,463],{"class":113},"\"testContainer\"",[65,465,466],{"class":74},", () ",[65,468,85],{"class":78},[65,470,88],{"class":74},[65,472,473,476,479,482,485],{"class":67,"line":11},[65,474,475],{"class":78},"  let",[65,477,478],{"class":74}," mongoContainer",[65,480,481],{"class":78},":",[65,483,484],{"class":70}," StartedTestContainer",[65,486,487],{"class":74},";\n",[65,489,490,492],{"class":67,"line":120},[65,491,475],{"class":78},[65,493,494],{"class":74}," mongoClient;\n",[65,496,497],{"class":67,"line":131},[65,498,499],{"emptyLinePlaceholder":16},"\n",[65,501,502,504,507,509,511],{"class":67,"line":199},[65,503,475],{"class":78},[65,505,506],{"class":74}," minioContainer",[65,508,481],{"class":78},[65,510,484],{"class":70},[65,512,487],{"class":74},[65,514,515,517],{"class":67,"line":205},[65,516,475],{"class":78},[65,518,519],{"class":74}," minioClient;\n",[65,521,522],{"class":67,"line":211},[65,523,499],{"emptyLinePlaceholder":16},[65,525,526,529,531,533,535,537],{"class":67,"line":217},[65,527,528],{"class":70},"  beforeAll",[65,530,75],{"class":74},[65,532,79],{"class":78},[65,534,82],{"class":74},[65,536,85],{"class":78},[65,538,88],{"class":74},[65,540,541,544,546,548,551],{"class":67,"line":222},[65,542,543],{"class":74},"    mongoContainer ",[65,545,96],{"class":78},[65,547,161],{"class":78},[65,549,550],{"class":70}," startMongoContainer",[65,552,553],{"class":74},"();\n",[65,555,556,559,561,563,566],{"class":67,"line":332},[65,557,558],{"class":74},"    mongoClient ",[65,560,96],{"class":78},[65,562,161],{"class":78},[65,564,565],{"class":70}," getMongoClient",[65,567,568],{"class":74},"({ container: mongoContainer });\n",[65,570,571],{"class":67,"line":342},[65,572,499],{"emptyLinePlaceholder":16},[65,574,575,578,580,582,585],{"class":67,"line":349},[65,576,577],{"class":74},"    minioContainer ",[65,579,96],{"class":78},[65,581,161],{"class":78},[65,583,584],{"class":70}," startMinioContainer",[65,586,553],{"class":74},[65,588,589,592,594,596,599],{"class":67,"line":357},[65,590,591],{"class":74},"    minioClient ",[65,593,96],{"class":78},[65,595,161],{"class":78},[65,597,598],{"class":70}," getMinioClient",[65,600,601],{"class":74},"({ container: minioContainer });\n",[65,603,604],{"class":67,"line":365},[65,605,606],{"class":74},"  });\n",[65,608,609],{"class":67,"line":376},[65,610,499],{"emptyLinePlaceholder":16},[65,612,613,616,618,620,622,624],{"class":67,"line":387},[65,614,615],{"class":70},"  afterAll",[65,617,75],{"class":74},[65,619,79],{"class":78},[65,621,82],{"class":74},[65,623,85],{"class":78},[65,625,88],{"class":74},[65,627,628,631,634,637],{"class":67,"line":398},[65,629,630],{"class":78},"    await",[65,632,633],{"class":74}," mongoContainer.",[65,635,636],{"class":70},"stop",[65,638,553],{"class":74},[65,640,642,644,647,649],{"class":67,"line":641},18,[65,643,630],{"class":78},[65,645,646],{"class":74}," minioContainer.",[65,648,636],{"class":70},[65,650,553],{"class":74},[65,652,654],{"class":67,"line":653},19,[65,655,606],{"class":74},[65,657,659],{"class":67,"line":658},20,[65,660,134],{"class":74},[35,662,663,664,667,668,671],{},"Basically you're creating containers from your test code to be able to have access to your Mongo\ndatabase and your Minio instance. Now let's see in details how the container are created inside the\n",[62,665,666],{},"startMongoContainer"," and ",[62,669,670],{},"startMinioContainer"," functions.",[56,673,675],{"className":58,"code":674,"language":60,"meta":10,"style":10},"async function startMongoContainer() {\n  const container = await new GenericContainer(\"mongo:4.2.6\")\n    .withExposedPorts({ container: 27017, host: 27018 })\n    .start();\n\n  return container;\n}\n\nasync function startMinioContainer() {\n  const container = await new GenericContainer(\"quay.io\u002Fminio\u002Fminio:RELEASE.2022-04-01T03-41-39Z\")\n    .withEnv(\"MINIO_ROOT_USER\", \"indy\")\n    .withEnv(\"MINIO_ROOT_PASSWORD\", \"indy\u002FAWS_ACCESS_KEY\u002FAWS_SECRET_ACCESS_KEY\")\n    .withEnv(\"MINIO_ACCESS_KEY\", \"indy\")\n    .withEnv(\"MINIO_SECRET_KEY\", \"indy\u002FAWS_ACCESS_KEY\u002FAWS_SECRET_ACCESS_KEY\")\n    .withExposedPorts({ container: 9000, host: 9999 })\n    .withCmd([\"server\", \"\u002Fdata\"])\n    .start();\n\n  return container;\n}\n",[62,676,677,689,716,739,748,752,760,765,769,779,800,820,838,855,872,890,911,919,923,929],{"__ignoreMap":10},[65,678,679,681,684,686],{"class":67,"line":18},[65,680,79],{"class":78},[65,682,683],{"class":78}," function",[65,685,550],{"class":70},[65,687,688],{"class":74},"() {\n",[65,690,691,694,697,700,702,705,708,710,713],{"class":67,"line":11},[65,692,693],{"class":78},"  const",[65,695,696],{"class":152}," container",[65,698,699],{"class":78}," =",[65,701,161],{"class":78},[65,703,704],{"class":78}," new",[65,706,707],{"class":70}," GenericContainer",[65,709,75],{"class":74},[65,711,712],{"class":113},"\"mongo:4.2.6\"",[65,714,715],{"class":74},")\n",[65,717,718,721,724,727,730,733,736],{"class":67,"line":120},[65,719,720],{"class":74},"    .",[65,722,723],{"class":70},"withExposedPorts",[65,725,726],{"class":74},"({ container: ",[65,728,729],{"class":152},"27017",[65,731,732],{"class":74},", host: ",[65,734,735],{"class":152},"27018",[65,737,738],{"class":74}," })\n",[65,740,741,743,746],{"class":67,"line":131},[65,742,720],{"class":74},[65,744,745],{"class":70},"start",[65,747,553],{"class":74},[65,749,750],{"class":67,"line":199},[65,751,499],{"emptyLinePlaceholder":16},[65,753,754,757],{"class":67,"line":205},[65,755,756],{"class":78},"  return",[65,758,759],{"class":74}," container;\n",[65,761,762],{"class":67,"line":211},[65,763,764],{"class":74},"}\n",[65,766,767],{"class":67,"line":217},[65,768,499],{"emptyLinePlaceholder":16},[65,770,771,773,775,777],{"class":67,"line":222},[65,772,79],{"class":78},[65,774,683],{"class":78},[65,776,584],{"class":70},[65,778,688],{"class":74},[65,780,781,783,785,787,789,791,793,795,798],{"class":67,"line":332},[65,782,693],{"class":78},[65,784,696],{"class":152},[65,786,699],{"class":78},[65,788,161],{"class":78},[65,790,704],{"class":78},[65,792,707],{"class":70},[65,794,75],{"class":74},[65,796,797],{"class":113},"\"quay.io\u002Fminio\u002Fminio:RELEASE.2022-04-01T03-41-39Z\"",[65,799,715],{"class":74},[65,801,802,804,807,809,812,815,818],{"class":67,"line":342},[65,803,720],{"class":74},[65,805,806],{"class":70},"withEnv",[65,808,75],{"class":74},[65,810,811],{"class":113},"\"MINIO_ROOT_USER\"",[65,813,814],{"class":74},", ",[65,816,817],{"class":113},"\"indy\"",[65,819,715],{"class":74},[65,821,822,824,826,828,831,833,836],{"class":67,"line":349},[65,823,720],{"class":74},[65,825,806],{"class":70},[65,827,75],{"class":74},[65,829,830],{"class":113},"\"MINIO_ROOT_PASSWORD\"",[65,832,814],{"class":74},[65,834,835],{"class":113},"\"indy\u002FAWS_ACCESS_KEY\u002FAWS_SECRET_ACCESS_KEY\"",[65,837,715],{"class":74},[65,839,840,842,844,846,849,851,853],{"class":67,"line":357},[65,841,720],{"class":74},[65,843,806],{"class":70},[65,845,75],{"class":74},[65,847,848],{"class":113},"\"MINIO_ACCESS_KEY\"",[65,850,814],{"class":74},[65,852,817],{"class":113},[65,854,715],{"class":74},[65,856,857,859,861,863,866,868,870],{"class":67,"line":365},[65,858,720],{"class":74},[65,860,806],{"class":70},[65,862,75],{"class":74},[65,864,865],{"class":113},"\"MINIO_SECRET_KEY\"",[65,867,814],{"class":74},[65,869,835],{"class":113},[65,871,715],{"class":74},[65,873,874,876,878,880,883,885,888],{"class":67,"line":376},[65,875,720],{"class":74},[65,877,723],{"class":70},[65,879,726],{"class":74},[65,881,882],{"class":152},"9000",[65,884,732],{"class":74},[65,886,887],{"class":152},"9999",[65,889,738],{"class":74},[65,891,892,894,897,900,903,905,908],{"class":67,"line":387},[65,893,720],{"class":74},[65,895,896],{"class":70},"withCmd",[65,898,899],{"class":74},"([",[65,901,902],{"class":113},"\"server\"",[65,904,814],{"class":74},[65,906,907],{"class":113},"\"\u002Fdata\"",[65,909,910],{"class":74},"])\n",[65,912,913,915,917],{"class":67,"line":398},[65,914,720],{"class":74},[65,916,745],{"class":70},[65,918,553],{"class":74},[65,920,921],{"class":67,"line":641},[65,922,499],{"emptyLinePlaceholder":16},[65,924,925,927],{"class":67,"line":653},[65,926,756],{"class":78},[65,928,759],{"class":74},[65,930,931],{"class":67,"line":658},[65,932,764],{"class":74},[35,934,935,936,939,940,671],{},"As you can see it's a simple way to create containers and to configure them directly in the code. We\ncan easily specify the Docker image to use, provide environment variables, expose ports and execute\na specific command to launch the container. Then we get the container instance that we can use to\ncreate our clients to consume the services. To see that let's dive in the ",[62,937,938],{},"getMongoClient"," and\n",[62,941,942],{},"getMinioClient",[56,944,946],{"className":58,"code":945,"language":60,"meta":10,"style":10},"async function getMongoClient({ container }: { container: StartedTestContainer }) {\n  const mongoClient = await new MongoClient(\n    `mongodb:\u002F\u002F${container.getHost()}:${container.getMappedPort(27017)}\u002F${config.mongo.dbName}`,\n  ).connect();\n\n  return mongoClient;\n}\n\nasync function getMinioClient({ container }: { container: StartedTestContainer }) {\n  const minioClient = new Minio.Client({\n    endPoint: container.getHost(),\n    port: container.getMappedPort(9000),\n    accessKey: config.aws.awsAccessKeyId,\n    secretKey: config.aws.awsSecretAccessKey,\n    useSSL: false,\n  });\n\n  return minioClient;\n}\n",[62,947,948,980,999,1053,1063,1067,1073,1077,1081,1107,1126,1136,1150,1155,1160,1170,1174,1178,1184],{"__ignoreMap":10},[65,949,950,952,954,956,959,963,966,968,971,973,975,977],{"class":67,"line":18},[65,951,79],{"class":78},[65,953,683],{"class":78},[65,955,565],{"class":70},[65,957,958],{"class":74},"({ ",[65,960,962],{"class":961},"s4XuR","container",[65,964,965],{"class":74}," }",[65,967,481],{"class":78},[65,969,970],{"class":74}," { ",[65,972,962],{"class":961},[65,974,481],{"class":78},[65,976,484],{"class":70},[65,978,979],{"class":74}," }) {\n",[65,981,982,984,987,989,991,993,996],{"class":67,"line":11},[65,983,693],{"class":78},[65,985,986],{"class":152}," mongoClient",[65,988,699],{"class":78},[65,990,161],{"class":78},[65,992,704],{"class":78},[65,994,995],{"class":70}," MongoClient",[65,997,998],{"class":74},"(\n",[65,1000,1001,1004,1006,1009,1012,1015,1018,1020,1022,1025,1027,1029,1032,1035,1038,1040,1043,1045,1048,1051],{"class":67,"line":120},[65,1002,1003],{"class":113},"    `mongodb:\u002F\u002F${",[65,1005,962],{"class":74},[65,1007,1008],{"class":113},".",[65,1010,1011],{"class":70},"getHost",[65,1013,1014],{"class":113},"()",[65,1016,1017],{"class":113},"}:${",[65,1019,962],{"class":74},[65,1021,1008],{"class":113},[65,1023,1024],{"class":70},"getMappedPort",[65,1026,75],{"class":113},[65,1028,729],{"class":152},[65,1030,1031],{"class":113},")",[65,1033,1034],{"class":113},"}\u002F${",[65,1036,1037],{"class":74},"config",[65,1039,1008],{"class":113},[65,1041,1042],{"class":74},"mongo",[65,1044,1008],{"class":113},[65,1046,1047],{"class":74},"dbName",[65,1049,1050],{"class":113},"}`",[65,1052,186],{"class":74},[65,1054,1055,1058,1061],{"class":67,"line":131},[65,1056,1057],{"class":74},"  ).",[65,1059,1060],{"class":70},"connect",[65,1062,553],{"class":74},[65,1064,1065],{"class":67,"line":199},[65,1066,499],{"emptyLinePlaceholder":16},[65,1068,1069,1071],{"class":67,"line":205},[65,1070,756],{"class":78},[65,1072,494],{"class":74},[65,1074,1075],{"class":67,"line":211},[65,1076,764],{"class":74},[65,1078,1079],{"class":67,"line":217},[65,1080,499],{"emptyLinePlaceholder":16},[65,1082,1083,1085,1087,1089,1091,1093,1095,1097,1099,1101,1103,1105],{"class":67,"line":222},[65,1084,79],{"class":78},[65,1086,683],{"class":78},[65,1088,598],{"class":70},[65,1090,958],{"class":74},[65,1092,962],{"class":961},[65,1094,965],{"class":74},[65,1096,481],{"class":78},[65,1098,970],{"class":74},[65,1100,962],{"class":961},[65,1102,481],{"class":78},[65,1104,484],{"class":70},[65,1106,979],{"class":74},[65,1108,1109,1111,1114,1116,1118,1121,1124],{"class":67,"line":332},[65,1110,693],{"class":78},[65,1112,1113],{"class":152}," minioClient",[65,1115,699],{"class":78},[65,1117,704],{"class":78},[65,1119,1120],{"class":74}," Minio.",[65,1122,1123],{"class":70},"Client",[65,1125,170],{"class":74},[65,1127,1128,1131,1133],{"class":67,"line":342},[65,1129,1130],{"class":74},"    endPoint: container.",[65,1132,1011],{"class":70},[65,1134,1135],{"class":74},"(),\n",[65,1137,1138,1141,1143,1145,1147],{"class":67,"line":349},[65,1139,1140],{"class":74},"    port: container.",[65,1142,1024],{"class":70},[65,1144,75],{"class":74},[65,1146,882],{"class":152},[65,1148,1149],{"class":74},"),\n",[65,1151,1152],{"class":67,"line":357},[65,1153,1154],{"class":74},"    accessKey: config.aws.awsAccessKeyId,\n",[65,1156,1157],{"class":67,"line":365},[65,1158,1159],{"class":74},"    secretKey: config.aws.awsSecretAccessKey,\n",[65,1161,1162,1165,1168],{"class":67,"line":376},[65,1163,1164],{"class":74},"    useSSL: ",[65,1166,1167],{"class":152},"false",[65,1169,186],{"class":74},[65,1171,1172],{"class":67,"line":387},[65,1173,606],{"class":74},[65,1175,1176],{"class":67,"line":398},[65,1177,499],{"emptyLinePlaceholder":16},[65,1179,1180,1182],{"class":67,"line":641},[65,1181,756],{"class":78},[65,1183,519],{"class":74},[65,1185,1186],{"class":67,"line":653},[65,1187,764],{"class":74},[35,1189,1190,1191,1194,1195,1198],{},"The container instance that we get from TestContainers is very convenient as it exposes various\nmethod to access the ",[62,1192,1193],{},"mappedPort",", the ",[62,1196,1197],{},"host",",… which is very useful to instantiate our clients.",[35,1200,1201],{},"Then we can easily run our tests using our containers and stop them at the end. We have the power of\nthe containers directly inside our tests which can be very helpful in a context of integration\ntesting. Another benefit is that you run your integration tests in the same environment in local and\nin the CI which is great for debug purposes.",[35,1203,1204],{},"I will end with 2 warning. Firstly be careful with the performances of your tests. Since we are\ncreating containers it might take some time especially if we compare to the mock solution (but still\nless than asking to your ops team to create a DB for your tests). Secondly, the TestContainers for\nNodeJS is fairly new compare to the original Java version so all the functionalities may not be\navailable yet and the overall stability of the project might be slightly below its counterpart.",[30,1206,1208],{"id":1207},"conclusion","Conclusion",[1210,1211,1213],"h3",{"id":1212},"what-does-testcontainers-bring-to-the-table","What does TestContainers bring to the table ?",[49,1215,1216,1219,1222,1225,1228],{},[52,1217,1218],{},"A great balance between usability, speed and features.",[52,1220,1221],{},"A CI agnostic tool that will encourage you to write more integration tests.",[52,1223,1224],{},"The freedom of writing integration tests that relies on external services without having to wait\nfor ops intervention.",[52,1226,1227],{},"It frees you from making assumption when you mock external services responses in your integration\ntests.",[52,1229,1230],{},"It allows you to run your integration tests in an environment as close as your CI and production\nenvironment.",[1210,1232,1234],{"id":1233},"what-is-the-cost","What is the cost ?",[49,1236,1237,1240],{},[52,1238,1239],{},"You need to have some kind of Docker running in your local machine, but it should be available no\nmatter if you're using Linux, MacOS or even Windows.",[52,1241,1242],{},"You need to be able to start containers in your CI environment, which might involve some ops\noperations…",[35,1244,1245],{},"This is a project that exists for many languages (Java, JavaScript, Go, Rust,…) and that seems to be\nused by various companies (JetBrains, Spring, Wise,…). It's quite easy to start and experiment with\nso I heavily encourage you to give it a try.",[30,1247,1249],{"id":1248},"resources","Resources",[35,1251,1252,1256,1257,1261],{},[1253,1254,1255],"em",{},"Main website"," :\n",[243,1258],{"href":1259,"rel":1260},"https:\u002F\u002Fwww.testcontainers.org\u002F",[247],[243,1262,1259],{"href":1259,"rel":1263},[247],[35,1265,1266,1256,1269,1272],{},[1253,1267,1268],{},"TestContainers NodeJS",[243,1270],{"href":442,"rel":1271},[247],[243,1273,442],{"href":442,"rel":1274},[247],[35,1276,1277,1256,1280,1284],{},[1253,1278,1279],{},"Interesting talk about integration tests and TestContainers",[243,1281],{"href":1282,"rel":1283},"https:\u002F\u002Fyoutu.be\u002FX2Qd4Myy-IY",[247],[243,1285,1282],{"href":1282,"rel":1286},[247],[1288,1289,1290],"style",{},"html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}",{"title":10,"searchDepth":11,"depth":11,"links":1292},[1293,1294,1295,1296,1297,1301],{"id":32,"depth":11,"text":33},{"id":43,"depth":11,"text":44},{"id":230,"depth":11,"text":231},{"id":426,"depth":11,"text":427},{"id":1207,"depth":11,"text":1208,"children":1298},[1299,1300],{"id":1212,"depth":120,"text":1213},{"id":1233,"depth":120,"text":1234},{"id":1248,"depth":11,"text":1249},"2022-10-06","Context Integration testing can be tricky to setup because of all external services that can be involved.","en",{},"\u002Farticles\u002F2022-10-06-3-different-ways-to-write-integration-tests-with-external-services-dependencies",{"title":25,"description":1303},"articles\u002F2022-10-06-3-different-ways-to-write-integration-tests-with-external-services-dependencies",[1310],"Tech","qF0Jx2Xc6D1eZs8AhRCHdDzXyI7GbcGFzryAlQeSSGc",{"id":1313,"title":1314,"author":26,"body":1315,"date":1479,"description":1480,"extension":13,"lang":1481,"meta":1482,"navigation":16,"path":1483,"published":16,"readingTime":217,"seo":1484,"stem":1485,"tags":1486,"__hash__":1487},"articles\u002Farticles\u002F2022-02-03-github-copilot.md","GitHub Copilot",{"type":7,"value":1316,"toc":1467},[1317,1321,1330,1334,1343,1349,1353,1357,1366,1370,1373,1378,1381,1384,1389,1396,1405,1408,1413,1416,1420,1423,1432,1436,1444,1448,1455,1459],[30,1318,1320],{"id":1319},"quest-ce","Qu'est ce ?",[35,1322,1323,1324,1329],{},"Si je cite la ",[243,1325,1328],{"href":1326,"rel":1327},"https:\u002F\u002Fcopilot.github.com",[247],"documentation"," il s'agit de son \"AI pair programmer\", ce\nque je comprends par là c'est, la personne assise à côté de moi, qui est censée m'aider, m'assister\nlorsque j'écris du code. Donc je me retrouve à faire du pair programming à longueur de temps avec un\ndéveloppeur virtuel ayant connaissance de toute la codebase publique de GitHub. En effet si je\nregarde la documentation encore une fois, j'apprends que le modèle a été entraîné sur des milliards\nde lignes de codes stockées sur GitHub. Ce n'est pas un \"simple\" outil d'auto-complétion, cela va\nplus loin, en regardant le contexte et en suggérant un bloc de code complet pour écrire une fonction\nou encore terminer logiquement la liste des constantes que j'ai commencé à saisir. L'idée étant de\ngagner du temps, en évitant les allers retours sur internet pour vérifier comment faire telle ou\ntelle chose ou encore trouver des exemples.",[30,1331,1333],{"id":1332},"comment-ça-fonctionne","Comment ça fonctionne ?",[35,1335,1336,1337,1342],{},"En installant le plugin dans son IDE favori (pour l'instant sont supportés: Visual Studio Code,\nNeovim et JetBrains) celui-ci va analyser le contexte en regardant les commentaires et le code pour\npouvoir proposer une suggestion. Il peut s'agir de la fin d'une ligne de code ou d'une fonction\nentière juste à partir du nom de celle-ci. GitHub Copilot s'appuie sur\n",[243,1338,1341],{"href":1339,"rel":1340},"https:\u002F\u002Fopenai.com\u002Fblog\u002Fopenai-codex",[247],"OpenAI Codex"," pour justement comprendre nos attentions en se\nbasant sur ce que nous avons déjà codé mais aussi sur ce que nous avons écrit en commentaire du\ncode. L'idée étant de choisir la meilleure solution proposée ou de modifier une proposition afin\nd'améliorer continuellement le modèle comme on peut le voir sur le schéma ci-dessous.",[35,1344,1345],{},[1346,1347],"img",{"alt":10,"src":1348},"\u002Fimages\u002Funtitled-2.png",[30,1350,1352],{"id":1351},"de-la-promesse-à-la-réalité-technical-preview","De la promesse à la réalité (Technical Preview)",[1210,1354,1356],{"id":1355},"des-débuts-compliqués","Des débuts compliqués",[35,1358,1359,1360,1365],{},"L'installation est super simple puisqu'il n'y a rien à faire à part installer l'extension pour son\nIDE préféré. Ensuite il faut avoir reçu son invitation par email après s'être\n",[243,1361,1364],{"href":1362,"rel":1363},"https:\u002F\u002Fgithub.com\u002Ffeatures\u002Fcopilot\u002Fsignup",[247],"inscrit"," pour la \"Technical Preview\". À partir de là\nles premiers soucis sont arrivés puisque la version du plugin que j'avais pour IntelliJ le faisait\nlittéralement planter à longueur de temps dès que j'écrivais la moindre ligne de code et sans rien\nme suggérer... FAUX DÉPART",[1210,1367,1369],{"id":1368},"la-lumière-au-bout-de-la-touche-tab","La lumière au bout de la touche TAB",[35,1371,1372],{},"Après une mise à jour du plugin il semblerait que tout soit rentré dans l'ordre et j'étais enfin\nprêt à devenir un meilleur développeur. Un exemple d'une première intervention très utile a été le\nsuivant, j'étais en train d'écrire le résultat attendu d'une fonction dans un test unitaire et\nGitHub Copilot est venu me filer un petit coup de main d'une manière très intelligente sur les\ndonnées que j'avais à compléter.",[35,1374,1375],{},[1346,1376],{"alt":10,"src":1377},"\u002Fimages\u002Fscreenshot-2021-11-25-at-17.17.07.png",[35,1379,1380],{},"Dans cet exemple on voit que je suis en train d'ajouter une propriété à mon objet et qu'il y a une\nsuite logique dans ces données, à savoir, les mois de l'année 2022. Il a donc été capable de\ns'alimenter du contexte de mon test et de la récurrence de la donnée pour me suggérer la bonne\nréponse: \"Avril 2022\". La bonne nouvelle c'est que la proposition est instantanée aucun\nralentissement ressenti et il n'y a pas eu non plus d'analyse du fichier en amont qui m'aurait\nempêchée d'avancer, tout se fait en temps réel.",[35,1382,1383],{},"Comme je le disais plus haut GitHub Copilot va plus loin que de l'auto complétion et essaie de nous\nfaire gagner du temps en essayant de deviner le code que l'on souhaite écrire. C'est ce qu'on va\nvoir dans l'exemple suivant où grâce au nom de la fonction et au contexte dans lequel se trouve\ncette fonction il me propose de tout écrire lui même...",[35,1385,1386],{},[1346,1387],{"alt":10,"src":1388},"\u002Fimages\u002Fscreenshot-2021-11-26-at-15.51.49.png",[35,1390,1391,1392,1395],{},"Dans cet exemple j'ai écris le nom de la fonction et ses paramètres, à partir de là il me suggère ce\nque pourrait être le corps de la fonction selon lui et c'est plutôt pas mal pour être honnête. Il a\ndétecté que j'utilisais la librairie ",[62,1393,1394],{},"moment.js"," et l'utilise donc pour calculer la différence entre\nles deux dates. Peut être que je ne souhaite pas avoir la différence en jours mais ceci étant dit si\nc'est la seule modification que j'ai à faire par la suite ça me semble être un véritable gain de\ntemps.",[35,1397,1399,1400],{"style":1398},"text-align: center;","\n  ",[1346,1401],{"src":1402,"alt":1403,"style":1404},"\u002Fimages\u002Fscreenshot-2021-12-03-at-15.56.25.png","Capture d'écran Copilot","display: inline-block; width: 300px; height: auto;",[35,1406,1407],{},"Dans cet exemple on prend le cas d'une suggestion spécifique à une technologie, en l'occurence nous\nsommes dans un contexte Vue avec Vue Router et je définis les routes pour mon application. De part\nla structure de mes routes il est capable de me suggérer ce que je vais vouloir faire pour celles en\ndessous de mon layout principal. Ici on voit clairement l'apprentissage sur le code de GitHub car on\nimagine assez bien que c'est un bout de code \"normal\" que de vouloir protéger ce genre de routes.",[35,1409,1410],{},[1346,1411],{"alt":10,"src":1412},"\u002Fimages\u002Fgithubcopilot.png",[35,1414,1415],{},"Un dernier exemple pour la route avant de conclure et pour mettre en avant la compréhension du\nlangage au delà du code. Ici on voit bien que c'est grâce à la description de mon test que GitHub\nCopilot est capable de me suggérer ce que je veux écrire. Je parle en effet de vouloir vérifier\nqu'il y a bien 12 éléments qui sont retournés par ma fonction et il me propose donc de tester que le\nrésultat de mon appel à une taille de 12 sans même que j'ai à écrire quoi que ce soit.",[1210,1417,1419],{"id":1418},"après-un-mois-dutilisation","Après un mois d'utilisation...",[35,1421,1422],{},"Globalement je trouve que c'est une réussite (surtout pour une Technical Preview), sur le fond,\nc'est à dire la pertinence de ce qui est proposé, c'est tout simplement impressionnant et\nvéritablement utile au quotidien. Sur la forme, c'est à dire la façon dont est suggéré le code en\nmode inline, c'est parfois un peu trop intrusif voir même perturbant pour s'y retrouver quand ça\nnous propose des gros pavés de plusieurs lignes de codes. Pas facile cependant de trouver un juste\nmilieu mais on peut imaginer que l'IA va s'améliorer avec le temps jusqu'à n'intervenir que quand on\nen a vraiment besoin.",[35,1424,1425,1426,1431],{},"En ce qui me concerne je pense continuer à l'utiliser au quotidien, je suis curieux de voir si les\nsuggestions vont s'améliorer avec temps et s'adapter à mes habitudes de code. Reste à voir si ce\nservice restera gratuit car GitHub parle déjà d'un potentiel d'une\n",[243,1427,1430],{"href":1428,"rel":1429},"https:\u002F\u002Fcopilot.github.com\u002F#faq-will-there-be-a-paid-version",[247],"version \"commerciale\"",". En attendant\nje vous invite à tester pour vous faire votre propre idée sur le sujet quitte à activer uniquement\nle plugin de temps en temps.",[30,1433,1435],{"id":1434},"où-vont-mes-données","Où vont mes données ?",[35,1437,1438,1439,1443],{},"Dans sa documentation GitHub précise que seul le contexte du fichier dans lequel on travaille est\ntransmis et qu'ensuite est stocké si oui ou non on accepte la suggestions qui a été faite. Pour plus\nde détails je vous invite à aller lire la\n",[243,1440,1328],{"href":1441,"rel":1442},"https:\u002F\u002Fcopilot.github.com\u002F#faq-how-is-the-data-that-github-copilot-collects-used",[247],"\nqui est complète sur ce sujet.",[30,1445,1447],{"id":1446},"les-biais-de-lapprentissage","Les biais de l'apprentissage",[35,1449,1450,1451,1008],{},"Si on utilise les suggestions et qu'on push notre code pour que celui-ci soit utilisé dans\nl'apprentissage de l'IA est-ce qu'on ne va pas se retrouver dans une boucle où tout le monde fait\ntout le temps pareil sans innovation ? Pire encore, des mauvaises pratiques pourraient se propager à\ngrande échelle ? Il en va de même pour les failles de sécurités qui existent sur les repos GitHub\ndont l'IA se sert pour faire son apprentissage, cependant GitHub précise que plusieurs outils sont\nmis en place pour filtrer et suggérer le code le plus secure possible. Plus de détails dans la\n",[243,1452,1328],{"href":1453,"rel":1454},"https:\u002F\u002Fcopilot.github.com\u002F#faq-can-github-copilot-introduce-insecure-code-in-its-suggestions",[247],[30,1456,1458],{"id":1457},"du-coup-cest-bon-on-peut-se-séparer-de-tous-les-développeurs","Du coup c'est bon on peut se séparer de tous les développeurs ?",[35,1460,1461,1462,1466],{},"Pour l'instant les deux idées principales du projet sont d'aider les développeurs existants en\nréduisant les tâches répétitives et en leur permettant de gagner du temps ainsi que rendre le code\nplus accessible à tous notamment pour ceux qui veulent débuter. Encore une fois je vous renvoie vers\nla\n",[243,1463,1328],{"href":1464,"rel":1465},"https:\u002F\u002Fcopilot.github.com\u002F#faq-how-will-advanced-code-generation-tools-like-github-copilot-affect-developer-jobs",[247],"\nqui aborde ce sujet.",{"title":10,"searchDepth":11,"depth":11,"links":1468},[1469,1470,1471,1476,1477,1478],{"id":1319,"depth":11,"text":1320},{"id":1332,"depth":11,"text":1333},{"id":1351,"depth":11,"text":1352,"children":1472},[1473,1474,1475],{"id":1355,"depth":120,"text":1356},{"id":1368,"depth":120,"text":1369},{"id":1418,"depth":120,"text":1419},{"id":1434,"depth":11,"text":1435},{"id":1446,"depth":11,"text":1447},{"id":1457,"depth":11,"text":1458},"2022-02-03","Ce n'est pas un \"simple\" outil d'auto-complétion...","fr",{},"\u002Farticles\u002F2022-02-03-github-copilot",{"title":1314,"description":1480},"articles\u002F2022-02-03-github-copilot",[1310],"kFriwxCmdmHAe7SBe9EaUstrMAVWzvgPbKMwEFvqars",[1489,1502,1514,1526,1539,1551,1563,1576,1589,1602,1614,1621,1634,1646,1658,1670,1682,1694,1706,1718,1731,1743,1755,1768,1780,1792],{"id":1490,"title":1491,"body":1492,"description":10,"extension":13,"meta":1496,"name":1497,"navigation":16,"path":1498,"readingTime":18,"seo":1499,"stem":1500,"__hash__":1501},"authors\u002Fauthors\u002Falexandre-guillon.md","Software Engineer",{"type":7,"value":1493,"toc":1494},[],{"title":10,"searchDepth":11,"depth":11,"links":1495},[],{},"Alexandre Guillon","\u002Fauthors\u002Falexandre-guillon",{"title":1491,"description":10},"authors\u002Falexandre-guillon","4tf48mjyjFNqItOHaulICbrjeCyMag1o6801uHeTz98",{"id":1503,"title":1491,"body":1504,"description":10,"extension":13,"meta":1508,"name":1509,"navigation":16,"path":1510,"readingTime":18,"seo":1511,"stem":1512,"__hash__":1513},"authors\u002Fauthors\u002Falexis-ablain.md",{"type":7,"value":1505,"toc":1506},[],{"title":10,"searchDepth":11,"depth":11,"links":1507},[],{},"Alexis Ablain","\u002Fauthors\u002Falexis-ablain",{"title":1491,"description":10},"authors\u002Falexis-ablain","_SIAtB7f-39e5t3GiJof81NP47s6MGo2n4gaHkTy1uQ",{"id":1515,"title":5,"body":1516,"description":10,"extension":13,"meta":1520,"name":1521,"navigation":16,"path":1522,"readingTime":18,"seo":1523,"stem":1524,"__hash__":1525},"authors\u002Fauthors\u002Faxel-shaita.md",{"type":7,"value":1517,"toc":1518},[],{"title":10,"searchDepth":11,"depth":11,"links":1519},[],{},"Axel Shaïta","\u002Fauthors\u002Faxel-shaita",{"title":5,"description":10},"authors\u002Faxel-shaita","fK0argUhsBkWLjpTAhY13oYLVzQthcEYkCEdtHWmIgE",{"id":1527,"title":1528,"body":1529,"description":10,"extension":13,"meta":1533,"name":1534,"navigation":16,"path":1535,"readingTime":18,"seo":1536,"stem":1537,"__hash__":1538},"authors\u002Fauthors\u002Fbaptiste-faure.md","Head of Talent Acquisition",{"type":7,"value":1530,"toc":1531},[],{"title":10,"searchDepth":11,"depth":11,"links":1532},[],{},"Baptiste Faure","\u002Fauthors\u002Fbaptiste-faure",{"title":1528,"description":10},"authors\u002Fbaptiste-faure","ELisToYtcgHmgdVWZkCclTPV6exZtfyXqhpx1jjbJHs",{"id":1540,"title":1491,"body":1541,"description":10,"extension":13,"meta":1545,"name":1546,"navigation":16,"path":1547,"readingTime":18,"seo":1548,"stem":1549,"__hash__":1550},"authors\u002Fauthors\u002Fbenjamin-bouillot.md",{"type":7,"value":1542,"toc":1543},[],{"title":10,"searchDepth":11,"depth":11,"links":1544},[],{},"Benjamin Bouillot","\u002Fauthors\u002Fbenjamin-bouillot",{"title":1491,"description":10},"authors\u002Fbenjamin-bouillot","tbhCFZyfTt7ZM5b5YgqQ2nhgnSTl8BweaQQryc87fHo",{"id":1552,"title":5,"body":1553,"description":10,"extension":13,"meta":1557,"name":1558,"navigation":16,"path":1559,"readingTime":18,"seo":1560,"stem":1561,"__hash__":1562},"authors\u002Fauthors\u002Fcedric-nicoloso.md",{"type":7,"value":1554,"toc":1555},[],{"title":10,"searchDepth":11,"depth":11,"links":1556},[],{},"Cédric Nicoloso","\u002Fauthors\u002Fcedric-nicoloso",{"title":5,"description":10},"authors\u002Fcedric-nicoloso","ibSoh4VZYiWYTuLOnZTedaAfcnvet1Q9H7ogW0LgorY",{"id":1564,"title":1565,"body":1566,"description":10,"extension":13,"meta":1570,"name":1571,"navigation":16,"path":1572,"readingTime":18,"seo":1573,"stem":1574,"__hash__":1575},"authors\u002Fauthors\u002Fdavid-touzet.md","Staff Engineer",{"type":7,"value":1567,"toc":1568},[],{"title":10,"searchDepth":11,"depth":11,"links":1569},[],{},"David Touzet","\u002Fauthors\u002Fdavid-touzet",{"title":1565,"description":10},"authors\u002Fdavid-touzet","dHWwnQxb1Ubt-WwXWEODGEo9AFoq1cJUhfg3kdnYSBM",{"id":1577,"title":1578,"body":1579,"description":10,"extension":13,"meta":1583,"name":1584,"navigation":16,"path":1585,"readingTime":18,"seo":1586,"stem":1587,"__hash__":1588},"authors\u002Fauthors\u002Feloise-chizat.md","Data Engineer",{"type":7,"value":1580,"toc":1581},[],{"title":10,"searchDepth":11,"depth":11,"links":1582},[],{},"Eloïse Chizat","\u002Fauthors\u002Feloise-chizat",{"title":1578,"description":10},"authors\u002Feloise-chizat","Utd72Vm9qT4hh2ZbFi6a2_nXw5Wb494Ed_HL1ra5yw8",{"id":1590,"title":1591,"body":1592,"description":10,"extension":13,"meta":1596,"name":1597,"navigation":16,"path":1598,"readingTime":18,"seo":1599,"stem":1600,"__hash__":1601},"authors\u002Fauthors\u002Femmanuel-auclair.md","Staff engineer",{"type":7,"value":1593,"toc":1594},[],{"title":10,"searchDepth":11,"depth":11,"links":1595},[],{},"Emmanuel Auclair","\u002Fauthors\u002Femmanuel-auclair",{"title":1591,"description":10},"authors\u002Femmanuel-auclair","MtsA8THNLEn0dTtYEIQaGwDuf7MjQL55IOeei5gugEg",{"id":1603,"title":1491,"body":1604,"description":10,"extension":13,"meta":1608,"name":1609,"navigation":16,"path":1610,"readingTime":18,"seo":1611,"stem":1612,"__hash__":1613},"authors\u002Fauthors\u002Fhoreb-parraud.md",{"type":7,"value":1605,"toc":1606},[],{"title":10,"searchDepth":11,"depth":11,"links":1607},[],{},"Horeb Parraud","\u002Fauthors\u002Fhoreb-parraud",{"title":1491,"description":10},"authors\u002Fhoreb-parraud","ajjsnUX4ohZI-ghMdbb92q_taWDkKXVZSLZXoAeLQtg",{"id":4,"title":5,"body":1615,"description":10,"extension":13,"meta":1619,"name":15,"navigation":16,"path":17,"readingTime":18,"seo":1620,"stem":20,"__hash__":21},{"type":7,"value":1616,"toc":1617},[],{"title":10,"searchDepth":11,"depth":11,"links":1618},[],{},{"title":5,"description":10},{"id":1622,"title":1623,"body":1624,"description":10,"extension":13,"meta":1628,"name":1629,"navigation":16,"path":1630,"readingTime":18,"seo":1631,"stem":1632,"__hash__":1633},"authors\u002Fauthors\u002Fjulien-tassin.md","Head of Engineering",{"type":7,"value":1625,"toc":1626},[],{"title":10,"searchDepth":11,"depth":11,"links":1627},[],{},"Julien Tassin","\u002Fauthors\u002Fjulien-tassin",{"title":1623,"description":10},"authors\u002Fjulien-tassin","iUIHI7SITje38Jh9X9uvYs4-VsHx4eCdt6hAlyLFG_o",{"id":1635,"title":1491,"body":1636,"description":10,"extension":13,"meta":1640,"name":1641,"navigation":16,"path":1642,"readingTime":18,"seo":1643,"stem":1644,"__hash__":1645},"authors\u002Fauthors\u002Flaurent-renard.md",{"type":7,"value":1637,"toc":1638},[],{"title":10,"searchDepth":11,"depth":11,"links":1639},[],{},"Laurent Renard","\u002Fauthors\u002Flaurent-renard",{"title":1491,"description":10},"authors\u002Flaurent-renard","5BP7Ed-pt1SQHjh0UJ1XUrlLTcdlFaDoKBCP4deHq8A",{"id":1647,"title":1491,"body":1648,"description":10,"extension":13,"meta":1652,"name":1653,"navigation":16,"path":1654,"readingTime":18,"seo":1655,"stem":1656,"__hash__":1657},"authors\u002Fauthors\u002Fleo-martin.md",{"type":7,"value":1649,"toc":1650},[],{"title":10,"searchDepth":11,"depth":11,"links":1651},[],{},"Léo Martin","\u002Fauthors\u002Fleo-martin",{"title":1491,"description":10},"authors\u002Fleo-martin","eYxCHkRgbGDV7shKdTA9s7Tu0zGV4yDGFoKR5MHQntY",{"id":1659,"title":1491,"body":1660,"description":10,"extension":13,"meta":1664,"name":1665,"navigation":16,"path":1666,"readingTime":18,"seo":1667,"stem":1668,"__hash__":1669},"authors\u002Fauthors\u002Floic-bousquet.md",{"type":7,"value":1661,"toc":1662},[],{"title":10,"searchDepth":11,"depth":11,"links":1663},[],{},"Loïc Bousquet","\u002Fauthors\u002Floic-bousquet",{"title":1491,"description":10},"authors\u002Floic-bousquet","ko12qZwiGL8XNjAoy9oWypPkIjr29Pbq7vhdtgldqeQ",{"id":1671,"title":1491,"body":1672,"description":10,"extension":13,"meta":1676,"name":1677,"navigation":16,"path":1678,"readingTime":18,"seo":1679,"stem":1680,"__hash__":1681},"authors\u002Fauthors\u002Floic-poullain.md",{"type":7,"value":1673,"toc":1674},[],{"title":10,"searchDepth":11,"depth":11,"links":1675},[],{},"Loïc Poullain","\u002Fauthors\u002Floic-poullain",{"title":1491,"description":10},"authors\u002Floic-poullain","oRIyJhFRTqxy5dLCYQ2OnYZ1DB-gLDUM-85vTSYuTF0",{"id":1683,"title":1578,"body":1684,"description":10,"extension":13,"meta":1688,"name":1689,"navigation":16,"path":1690,"readingTime":18,"seo":1691,"stem":1692,"__hash__":1693},"authors\u002Fauthors\u002Fmaud-lelu.md",{"type":7,"value":1685,"toc":1686},[],{"title":10,"searchDepth":11,"depth":11,"links":1687},[],{},"Maud Lélu","\u002Fauthors\u002Fmaud-lelu",{"title":1578,"description":10},"authors\u002Fmaud-lelu","MMbsCKuE41OMHusrl12FIEsI-Trx7l8Nn_ANhvj2_y4",{"id":1695,"title":5,"body":1696,"description":10,"extension":13,"meta":1700,"name":1701,"navigation":16,"path":1702,"readingTime":18,"seo":1703,"stem":1704,"__hash__":1705},"authors\u002Fauthors\u002Fnicolas-poirier.md",{"type":7,"value":1697,"toc":1698},[],{"title":10,"searchDepth":11,"depth":11,"links":1699},[],{},"Nicolas Poirier","\u002Fauthors\u002Fnicolas-poirier",{"title":5,"description":10},"authors\u002Fnicolas-poirier","dXrJkYo8az4SN_D23aYc3fQ7z8s1dR2a0lt1ogjAjJs",{"id":1707,"title":5,"body":1708,"description":10,"extension":13,"meta":1712,"name":1713,"navigation":16,"path":1714,"readingTime":18,"seo":1715,"stem":1716,"__hash__":1717},"authors\u002Fauthors\u002Fraphael-sauget.md",{"type":7,"value":1709,"toc":1710},[],{"title":10,"searchDepth":11,"depth":11,"links":1711},[],{},"Raphaël Sauget","\u002Fauthors\u002Fraphael-sauget",{"title":5,"description":10},"authors\u002Fraphael-sauget","Uri9bcq0QDuxRA0PbBoNtu7p_5L3dALu4kzcXVW0xyM",{"id":1719,"title":1720,"body":1721,"description":10,"extension":13,"meta":1725,"name":1726,"navigation":16,"path":1727,"readingTime":18,"seo":1728,"stem":1729,"__hash__":1730},"authors\u002Fauthors\u002Fromain-koenig.md","Co-funder & Head of innovation",{"type":7,"value":1722,"toc":1723},[],{"title":10,"searchDepth":11,"depth":11,"links":1724},[],{},"Romain Koenig","\u002Fauthors\u002Fromain-koenig",{"title":1720,"description":10},"authors\u002Fromain-koenig","uyS8--eG2_ezyqRABcJnMJmQKKuSArhPWd14aUvFeEw",{"id":1732,"title":5,"body":1733,"description":10,"extension":13,"meta":1737,"name":1738,"navigation":16,"path":1739,"readingTime":18,"seo":1740,"stem":1741,"__hash__":1742},"authors\u002Fauthors\u002Fromaric-juniet.md",{"type":7,"value":1734,"toc":1735},[],{"title":10,"searchDepth":11,"depth":11,"links":1736},[],{},"Romaric Juniet","\u002Fauthors\u002Fromaric-juniet",{"title":5,"description":10},"authors\u002Fromaric-juniet","4Zb2artgT-eo-PHLXi3xi4d5t7s6PfhUxeSfXIikSUY",{"id":1744,"title":1491,"body":1745,"description":10,"extension":13,"meta":1749,"name":1750,"navigation":16,"path":1751,"readingTime":18,"seo":1752,"stem":1753,"__hash__":1754},"authors\u002Fauthors\u002Fstanyslas-bres.md",{"type":7,"value":1746,"toc":1747},[],{"title":10,"searchDepth":11,"depth":11,"links":1748},[],{},"Stanyslas Bres","\u002Fauthors\u002Fstanyslas-bres",{"title":1491,"description":10},"authors\u002Fstanyslas-bres","Xa0SahETuiN4q1jrmR2ych3moAqcZ2LbU7vSfEt2RuU",{"id":1756,"title":1757,"body":1758,"description":10,"extension":13,"meta":1762,"name":1763,"navigation":16,"path":1764,"readingTime":18,"seo":1765,"stem":1766,"__hash__":1767},"authors\u002Fauthors\u002Ftalent-acquisition.md","Talent Acquisition",{"type":7,"value":1759,"toc":1760},[],{"title":10,"searchDepth":11,"depth":11,"links":1761},[],{},"Équipe Talent Acquisition","\u002Fauthors\u002Ftalent-acquisition",{"description":10},"authors\u002Ftalent-acquisition","doDfE76txftQ4wIiKjJoDmSpyzSKk0tzlgVAp6-opAY",{"id":1769,"title":1491,"body":1770,"description":10,"extension":13,"meta":1774,"name":1775,"navigation":16,"path":1776,"readingTime":18,"seo":1777,"stem":1778,"__hash__":1779},"authors\u002Fauthors\u002Fvictor-borg.md",{"type":7,"value":1771,"toc":1772},[],{"title":10,"searchDepth":11,"depth":11,"links":1773},[],{},"Victor Borg","\u002Fauthors\u002Fvictor-borg",{"title":1491,"description":10},"authors\u002Fvictor-borg","-Za-JweoiP6hyclue_WkxMXdRUDTczPGlJf6AZckjUc",{"id":1781,"title":1491,"body":1782,"description":10,"extension":13,"meta":1786,"name":1787,"navigation":16,"path":1788,"readingTime":18,"seo":1789,"stem":1790,"__hash__":1791},"authors\u002Fauthors\u002Fvirgil-roger.md",{"type":7,"value":1783,"toc":1784},[],{"title":10,"searchDepth":11,"depth":11,"links":1785},[],{},"Virgil Roger","\u002Fauthors\u002Fvirgil-roger",{"title":1491,"description":10},"authors\u002Fvirgil-roger","DfVFe5j0bCgXeEr381ZYOM5DP4m-pWb93J9-m_muKJ0",{"id":1793,"title":1491,"body":1794,"description":10,"extension":13,"meta":1798,"name":1799,"navigation":16,"path":1800,"readingTime":18,"seo":1801,"stem":1802,"__hash__":1803},"authors\u002Fauthors\u002Fyukan-zhao.md",{"type":7,"value":1795,"toc":1796},[],{"title":10,"searchDepth":11,"depth":11,"links":1797},[],{},"Yukan Zhao","\u002Fauthors\u002Fyukan-zhao",{"title":1491,"description":10},"authors\u002Fyukan-zhao","LRPHugtAJnWHsmHxy9_SR5Zas_C5p-GR_uHEs1Fhk_E",1777562092996]